mirror of
https://github.com/lone-cloud/gerbil
synced 2026-06-04 04:04:44 -07:00
unite all warning and info detection into a single useWarnings hook
This commit is contained in:
parent
3953b13794
commit
70245e6a1f
5 changed files with 302 additions and 236 deletions
|
|
@ -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 (
|
||||
<div>
|
||||
<Group justify="space-between" align="flex-start" mb="xs">
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<Stack gap="md">
|
||||
<BackendSelector
|
||||
onWarningsChange={onWarningsChange}
|
||||
onBackendsReady={onBackendsReady}
|
||||
/>
|
||||
<BackendSelector onBackendsReady={onBackendsReady} />
|
||||
|
||||
<div>
|
||||
<Text size="sm" fw={500} mb="xs">
|
||||
|
|
|
|||
|
|
@ -25,9 +25,6 @@ export const LaunchScreen = ({
|
|||
const [selectedFile, setSelectedFile] = useState<string | null>(null);
|
||||
const [, setInstallDir] = useState<string>('');
|
||||
const [activeTab, setActiveTab] = useState<string | null>('general');
|
||||
const [warnings, setWarnings] = useState<
|
||||
Array<{ type: 'warning' | 'info'; message: string }>
|
||||
>([]);
|
||||
const [configLoaded, setConfigLoaded] = useState<boolean>(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 = ({
|
|||
|
||||
<div>
|
||||
<Tabs.Panel value="general" pt="md">
|
||||
<GeneralTab onWarningsChange={setWarnings} />
|
||||
<GeneralTab />
|
||||
</Tabs.Panel>
|
||||
|
||||
<Tabs.Panel value="advanced" pt="md">
|
||||
|
|
|
|||
|
|
@ -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<Warning[]> => {
|
||||
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<Warning[]> => {
|
||||
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<Warning[]> => {
|
||||
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<Warning[]>([]);
|
||||
|
||||
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,
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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<BackendWarning[]> => {
|
||||
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;
|
||||
}
|
||||
};
|
||||
Loading…
Add table
Reference in a new issue