diff --git a/src/components/screens/Launch/GeneralTab/BackendSelector.tsx b/src/components/screens/Launch/GeneralTab/BackendSelector.tsx index d886c7b..77074a5 100644 --- a/src/components/screens/Launch/GeneralTab/BackendSelector.tsx +++ b/src/components/screens/Launch/GeneralTab/BackendSelector.tsx @@ -3,24 +3,15 @@ import { useState, useEffect, useRef } from 'react'; import { InfoTooltip } from '@/components/InfoTooltip'; import { BackendSelectItem } from '@/components/screens/Launch/GeneralTab/BackendSelectItem'; import { useLaunchConfig } from '@/hooks/useLaunchConfig'; -import { checkBackendWarnings } from '@/utils/backendWarnings'; interface BackendSelectorProps { - onWarningsChange?: ( - warnings: Array<{ type: 'warning' | 'info'; message: string }> - ) => void; onBackendsReady?: () => void; } -export const BackendSelector = ({ - onWarningsChange, - onBackendsReady, -}: BackendSelectorProps) => { +export const BackendSelector = ({ onBackendsReady }: BackendSelectorProps) => { const { backend, gpuDevice, - noavx2, - failsafe, gpuLayers, autoGpuLayers, handleBackendChange, @@ -32,10 +23,6 @@ export const BackendSelector = ({ const [availableBackends, setAvailableBackends] = useState< Array<{ value: string; label: string; devices?: string[] }> >([]); - const [cpuCapabilities, setCpuCapabilities] = useState<{ - avx: boolean; - avx2: boolean; - } | null>(null); const hasInitialized = useRef(false); useEffect(() => { @@ -46,11 +33,6 @@ export const BackendSelector = ({ window.electronAPI.kobold.getAvailableBackends(), ]); - setCpuCapabilities({ - avx: cpuCapabilitiesResult.avx, - avx2: cpuCapabilitiesResult.avx2, - }); - const cpuBackend = backends.find((b) => b.value === 'cpu'); if (cpuBackend) { cpuBackend.devices = cpuCapabilitiesResult.devices; @@ -87,38 +69,6 @@ export const BackendSelector = ({ return cleanup; }, [onBackendsReady]); - useEffect(() => { - if (!onWarningsChange) return; - - const updateWarnings = async () => { - try { - const warnings = await checkBackendWarnings({ - backend, - cpuCapabilities, - noavx2, - failsafe, - availableBackends, - }); - onWarningsChange(warnings); - } catch (error) { - window.electronAPI.logs.logError( - 'Failed to check backend warnings:', - error as Error - ); - onWarningsChange([]); - } - }; - - updateWarnings(); - }, [ - backend, - cpuCapabilities, - noavx2, - failsafe, - availableBackends, - onWarningsChange, - ]); - return (
diff --git a/src/components/screens/Launch/GeneralTab/index.tsx b/src/components/screens/Launch/GeneralTab/index.tsx index f9afeca..e600ee0 100644 --- a/src/components/screens/Launch/GeneralTab/index.tsx +++ b/src/components/screens/Launch/GeneralTab/index.tsx @@ -7,16 +7,10 @@ import { useLaunchConfig } from '@/hooks/useLaunchConfig'; import styles from '@/styles/layout.module.css'; interface GeneralTabProps { - onWarningsChange?: ( - warnings: Array<{ type: 'warning' | 'info'; message: string }> - ) => void; onBackendsReady?: () => void; } -export const GeneralTab = ({ - onWarningsChange, - onBackendsReady, -}: GeneralTabProps) => { +export const GeneralTab = ({ onBackendsReady }: GeneralTabProps) => { const { modelPath, contextSize, @@ -50,10 +44,7 @@ export const GeneralTab = ({ return ( - +
diff --git a/src/components/screens/Launch/index.tsx b/src/components/screens/Launch/index.tsx index 4df9156..0e8dde0 100644 --- a/src/components/screens/Launch/index.tsx +++ b/src/components/screens/Launch/index.tsx @@ -25,9 +25,6 @@ export const LaunchScreen = ({ const [selectedFile, setSelectedFile] = useState(null); const [, setInstallDir] = useState(''); const [activeTab, setActiveTab] = useState('general'); - const [warnings, setWarnings] = useState< - Array<{ type: 'warning' | 'info'; message: string }> - >([]); const [configLoaded, setConfigLoaded] = useState(false); const { @@ -73,7 +70,14 @@ export const LaunchScreen = ({ onLaunchModeChange, }); - const combinedWarnings = useWarnings({ modelPath, sdmodel, warnings }); + const { warnings: combinedWarnings } = useWarnings({ + modelPath, + sdmodel, + backend, + noavx2, + failsafe, + configLoaded, + }); const setHappyDefaults = useCallback(async () => { try { @@ -302,7 +306,7 @@ export const LaunchScreen = ({
- + diff --git a/src/hooks/useWarnings.ts b/src/hooks/useWarnings.ts index 2a6382e..059b178 100644 --- a/src/hooks/useWarnings.ts +++ b/src/hooks/useWarnings.ts @@ -1,41 +1,300 @@ -import { useMemo } from 'react'; +import { useMemo, useEffect, useState, useCallback } from 'react'; + +export interface Warning { + type: 'warning' | 'info'; + message: string; +} interface UseWarningsProps { modelPath: string; sdmodel: string; - warnings: Array<{ type: 'warning' | 'info'; message: string }>; + backend?: string; + noavx2?: boolean; + failsafe?: boolean; + configLoaded?: boolean; } +const checkModelWarnings = ( + modelPath: string, + sdmodel: string, + configLoaded: boolean +): Warning[] => { + const hasTextModel = modelPath?.trim() !== ''; + const hasImageModel = sdmodel.trim() !== ''; + const showModelPriorityWarning = hasTextModel && hasImageModel; + const showNoModelWarning = !hasTextModel && !hasImageModel && configLoaded; + + const warnings: Warning[] = []; + + if (showModelPriorityWarning) { + warnings.push({ + type: 'warning', + message: + 'Both text and image generation models are selected. The image generation model will take priority and be used for launch.', + }); + } + + if (showNoModelWarning) { + warnings.push({ + type: 'info', + message: + 'Select a model in the General or Image Generation tab to enable launch.', + }); + } + + return warnings; +}; + +interface BackendSupport { + cuda: boolean; + rocm: boolean; +} + +interface GpuCapabilities { + cuda: { supported: boolean }; + rocm: { supported: boolean }; +} + +interface GpuInfo { + hasNVIDIA: boolean; + hasAMD: boolean; +} + +const checkGpuWarnings = async ( + backendSupport: BackendSupport, + gpuCapabilities: GpuCapabilities, + gpuInfo: GpuInfo +): Promise => { + const warnings: Warning[] = []; + + if ( + backendSupport.cuda && + !gpuCapabilities.cuda.supported && + gpuInfo.hasNVIDIA + ) { + warnings.push({ + type: 'warning', + message: + 'Your KoboldCpp binary supports CUDA and you have an NVIDIA GPU, but CUDA runtime is not detected on your system.', + }); + } + + if ( + backendSupport.rocm && + !gpuCapabilities.rocm.supported && + gpuInfo.hasAMD + ) { + warnings.push({ + type: 'warning', + message: + 'Your KoboldCpp binary supports ROCm and you have an AMD GPU, but ROCm runtime is not detected on your system.', + }); + } + + return warnings; +}; + +const checkVramWarnings = async (backend: string): Promise => { + const warnings: Warning[] = []; + const isGpuBackend = ['cuda', 'rocm', 'vulkan', 'clblast'].includes(backend); + + if (isGpuBackend) { + try { + const gpuMemoryInfo = await window.electronAPI.kobold.detectGPUMemory(); + const lowVramGpus = gpuMemoryInfo.filter( + (gpu) => + typeof gpu.totalMemoryMB === 'number' && gpu.totalMemoryMB < 8192 + ); + + if (lowVramGpus.length > 0) { + warnings.push({ + type: 'warning', + message: `Low VRAM detected (${lowVramGpus + .map( + (gpu) => + `${gpu.deviceName}: ${(gpu.totalMemoryMB! / 1024).toFixed(1)}GB` + ) + .join( + ', ' + )}). Consider using smaller models, reducing GPU layers, or enabling the "Low VRAM" option on the Advanced tab.`, + }); + } + } catch (error) { + window.electronAPI.logs.logError( + 'Failed to detect GPU memory:', + error as Error + ); + } + } + + return warnings; +}; + +const checkCpuWarnings = ( + backend: string, + cpuCapabilities: { avx: boolean; avx2: boolean }, + noavx2: boolean, + failsafe: boolean, + availableBackends: Array<{ value: string; label: string; devices?: string[] }> +): Warning[] => { + const warnings: Warning[] = []; + + if (backend !== 'cpu') { + return warnings; + } + + if (!cpuCapabilities.avx2 && !noavx2) { + warnings.push({ + type: 'warning', + message: + 'Your CPU does not support AVX2. Enable the "Disable AVX2" option on the Advanced tab to avoid crashes.', + }); + } + + if (!cpuCapabilities.avx && !cpuCapabilities.avx2 && !failsafe) { + warnings.push({ + type: 'warning', + message: + 'Your CPU does not support AVX or AVX2. Enable the "Failsafe" option on the Advanced tab to avoid crashes.', + }); + } + + if ( + availableBackends.length > 0 && + availableBackends.some((b) => b.value === 'cpu') + ) { + warnings.push({ + type: 'info', + message: + "LLMs run significantly faster on GPU-accelerated systems. Consider using NVIDIA's CUDA, AMD's ROCm or Vulkan backends for optimal performance.", + }); + } + + return warnings; +}; + +const checkBackendWarnings = async (params?: { + backend: string; + cpuCapabilities: { + avx: boolean; + avx2: boolean; + } | null; + noavx2: boolean; + failsafe: boolean; + availableBackends: Array<{ + value: string; + label: string; + devices?: string[]; + }>; +}): Promise => { + const warnings: Warning[] = []; + + try { + const [backendSupport, gpuCapabilities, gpuInfo] = await Promise.all([ + window.electronAPI.kobold.detectBackendSupport(), + window.electronAPI.kobold.detectGPUCapabilities(), + window.electronAPI.kobold.detectGPU(), + ]); + + if (!backendSupport) { + return warnings; + } + + const gpuWarnings = await checkGpuWarnings( + backendSupport, + gpuCapabilities, + gpuInfo + ); + warnings.push(...gpuWarnings); + + if (params) { + const { backend, cpuCapabilities, noavx2, failsafe, availableBackends } = + params; + + const vramWarnings = await checkVramWarnings(backend); + warnings.push(...vramWarnings); + + if (cpuCapabilities) { + const cpuWarnings = checkCpuWarnings( + backend, + cpuCapabilities, + noavx2, + failsafe, + availableBackends + ); + warnings.push(...cpuWarnings); + } + } + + return warnings; + } catch (error) { + window.electronAPI.logs.logError( + 'Failed to check backend warnings:', + error as Error + ); + return warnings; + } +}; + export const useWarnings = ({ modelPath, sdmodel, - warnings, -}: UseWarningsProps) => - useMemo(() => { - const hasTextModel = modelPath?.trim() !== ''; - const hasImageModel = sdmodel.trim() !== ''; - const showModelPriorityWarning = hasTextModel && hasImageModel; - const showNoModelWarning = !hasTextModel && !hasImageModel; + backend, + noavx2 = false, + failsafe = false, + configLoaded = false, +}: UseWarningsProps) => { + const [backendWarnings, setBackendWarnings] = useState([]); - return [ - ...warnings, - ...(showModelPriorityWarning - ? [ - { - type: 'warning' as const, - message: - 'Both text and image generation models are selected. The image generation model will take priority and be used for launch.', - }, - ] - : []), - ...(showNoModelWarning - ? [ - { - type: 'info' as const, - message: - 'Select a model in the General or Image Generation tab to enable launch.', - }, - ] - : []), - ]; - }, [modelPath, sdmodel, warnings]); + const modelWarnings = useMemo( + () => checkModelWarnings(modelPath, sdmodel, configLoaded), + [modelPath, sdmodel, configLoaded] + ); + + const updateBackendWarnings = useCallback(async () => { + if (!backend) { + setBackendWarnings([]); + return; + } + + try { + const [cpuCapabilitiesResult, availableBackends] = await Promise.all([ + window.electronAPI.kobold.detectCPU(), + window.electronAPI.kobold.getAvailableBackends(), + ]); + + const cpuCapabilities = { + avx: cpuCapabilitiesResult.avx, + avx2: cpuCapabilitiesResult.avx2, + }; + + const warnings = await checkBackendWarnings({ + backend, + cpuCapabilities, + noavx2, + failsafe, + availableBackends, + }); + setBackendWarnings(warnings); + } catch (error) { + window.electronAPI.logs.logError( + 'Failed to check backend warnings:', + error as Error + ); + setBackendWarnings([]); + } + }, [backend, noavx2, failsafe]); + + useEffect(() => { + updateBackendWarnings(); + }, [updateBackendWarnings]); + + const allWarnings = useMemo( + () => [...modelWarnings, ...backendWarnings], + [modelWarnings, backendWarnings] + ); + + return { + warnings: allWarnings, + }; +}; diff --git a/src/utils/backendWarnings.ts b/src/utils/backendWarnings.ts deleted file mode 100644 index 0ee1875..0000000 --- a/src/utils/backendWarnings.ts +++ /dev/null @@ -1,138 +0,0 @@ -export interface BackendWarning { - type: 'warning' | 'info'; - message: string; -} - -interface WarningParams { - backend: string; - cpuCapabilities: { - avx: boolean; - avx2: boolean; - } | null; - noavx2: boolean; - failsafe: boolean; - availableBackends: Array<{ - value: string; - label: string; - devices?: string[]; - }>; -} - -export const checkBackendWarnings = async ( - params?: WarningParams - // eslint-disable-next-line sonarjs/cognitive-complexity -): Promise => { - const warnings: BackendWarning[] = []; - - try { - const [backendSupport, gpuCapabilities, gpuInfo] = await Promise.all([ - window.electronAPI.kobold.detectBackendSupport(), - window.electronAPI.kobold.detectGPUCapabilities(), - window.electronAPI.kobold.detectGPU(), - ]); - - if (!backendSupport) { - return warnings; - } - - if ( - backendSupport.cuda && - !gpuCapabilities.cuda.supported && - gpuInfo.hasNVIDIA - ) { - warnings.push({ - type: 'warning', - message: - 'Your KoboldCpp binary supports CUDA and you have an NVIDIA GPU, but CUDA runtime is not detected on your system.', - }); - } - - if ( - backendSupport.rocm && - !gpuCapabilities.rocm.supported && - gpuInfo.hasAMD - ) { - warnings.push({ - type: 'warning', - message: - 'Your KoboldCpp binary supports ROCm and you have an AMD GPU, but ROCm runtime is not detected on your system.', - }); - } - - if (params) { - const { backend, cpuCapabilities, noavx2, failsafe, availableBackends } = - params; - - const isGpuBackend = ['cuda', 'rocm', 'vulkan', 'clblast'].includes( - backend - ); - - if (isGpuBackend) { - try { - const gpuMemoryInfo = - await window.electronAPI.kobold.detectGPUMemory(); - const lowVramGpus = gpuMemoryInfo.filter( - (gpu) => - typeof gpu.totalMemoryMB === 'number' && gpu.totalMemoryMB < 8192 - ); - - if (lowVramGpus.length > 0) { - warnings.push({ - type: 'warning', - message: `Low VRAM detected (${lowVramGpus - .map( - (gpu) => - `${gpu.deviceName}: ${(gpu.totalMemoryMB! / 1024).toFixed(1)}GB` - ) - .join( - ', ' - )}). Consider using smaller models, reducing GPU layers, or enabling the "Low VRAM" option on the Advanced tab.`, - }); - } - } catch (error) { - window.electronAPI.logs.logError( - 'Failed to detect GPU memory:', - error as Error - ); - } - } - - if (backend === 'cpu' && cpuCapabilities) { - if (!cpuCapabilities.avx2 && !noavx2) { - warnings.push({ - type: 'warning', - message: - 'Your CPU does not support AVX2. Enable the "Disable AVX2" option on the Advanced tab to avoid crashes.', - }); - } - - if (!cpuCapabilities.avx && !cpuCapabilities.avx2 && !failsafe) { - warnings.push({ - type: 'warning', - message: - 'Your CPU does not support AVX or AVX2. Enable the "Failsafe" option on the Advanced tab to avoid crashes.', - }); - } - - if ( - availableBackends.length > 0 && - availableBackends.some((b) => b.value === 'cpu') - ) { - warnings.push({ - type: 'info', - message: - "LLMs run significantly faster on GPU-accelerated systems. Consider using NVIDIA's CUDA, AMD's ROCm or Vulkan backends for optimal performance.", - }); - } - } - } - - return warnings; - } catch (error) { - window.electronAPI.logs.logError( - 'Failed to check backend warnings:', - error as Error - ); - return warnings; - } -};