refactoring out duplicate code

This commit is contained in:
Egor 2025-08-21 16:14:01 -07:00
parent ded8acb436
commit 95dafe66f8
7 changed files with 290 additions and 309 deletions

View file

@ -1,44 +1,40 @@
AMDGPU
APPIMAGE
BLAS
CUDA
GGUF
KOBOLDAI
KOBOLDCPP
OLDPC
ROCM
SDXL
SPACEBAR
Consolas
Cooldown
Dolfino
Flashattention
KoboldCpp
Lowvram
Segoe
Tauri
asar
Autoencoder
BLAS
clblast
clinfo
Consolas
contextsize
Cooldown
cublas
cuda
CUDA
Dolfino
finetuned
flashattention
Flashattention
friendlykobold
geforce
ggml
gguf
GGUF
gpulayers
hipblas
kcpps
kcppt
koboldai
KOBOLDAI
koboldcpp
KoboldCpp
KOBOLDCPP
lora
lowvram
Lowvram
maximizable
minimizable
MMAP
mmproj
mmq
multiuser
@ -51,12 +47,14 @@ noshift
nsis
nvidia
oldpc
OLDPC
opencl
pkexec
quantmatmul
radeon
remotetunnel
rocm
ROCM
rocminfo
safetensors
sdclipg
@ -66,8 +64,12 @@ sdmodel
sdphotomaker
sdui
sdvae
SDXL
Segoe
sonarjs
SPACEBAR
taskkill
Tauri
togglefullscreen
treemap
trycloudflare
@ -81,4 +83,3 @@ vulkan
vulkaninfo
wayland
websearch
MMAP

View file

@ -0,0 +1,31 @@
import { Checkbox, Group } from '@mantine/core';
import { InfoTooltip } from '@/components/InfoTooltip';
import styles from '@/styles/layout.module.css';
interface CheckboxWithTooltipProps {
checked: boolean;
onChange: (checked: boolean) => void;
label: string;
tooltip: string;
disabled?: boolean;
}
export const CheckboxWithTooltip = ({
checked,
onChange,
label,
tooltip,
disabled = false,
}: CheckboxWithTooltipProps) => (
<div className={styles.minWidth200}>
<Group gap="xs" align="center">
<Checkbox
checked={checked}
onChange={(event) => onChange(event.currentTarget.checked)}
label={label}
disabled={disabled}
/>
<InfoTooltip label={tooltip} />
</Group>
</div>
);

View file

@ -0,0 +1,90 @@
import { Group, TextInput, Button } from '@mantine/core';
import { File, Search } from 'lucide-react';
import { SectionHeader } from '@/components/SectionHeader';
import { getInputValidationState } from '@/utils';
import styles from '@/styles/layout.module.css';
interface ModelFileFieldProps {
label: string;
value: string;
placeholder: string;
tooltip?: string;
onChange: (value: string) => void;
onSelectFile: () => void;
showSearchHF?: boolean;
searchUrl?: string;
}
export const ModelFileField = ({
label,
value,
placeholder,
tooltip,
onChange,
onSelectFile,
showSearchHF = false,
searchUrl = 'https://huggingface.co/models?pipeline_tag=text-to-image&library=gguf&sort=trending',
}: ModelFileFieldProps) => {
const validationState = getInputValidationState(value);
const getInputColor = () => {
switch (validationState) {
case 'valid':
return 'green';
case 'invalid':
return 'red';
default:
return undefined;
}
};
const getHelperText = () => {
if (!value.trim()) return undefined;
if (validationState === 'invalid') {
return 'Enter a valid URL or file path';
}
return undefined;
};
return (
<div>
<SectionHeader
title={label}
tooltip={tooltip}
fontWeight={500}
marginBottom="xs"
/>
<Group gap="xs" align="flex-start">
<div className={styles.flex1}>
<TextInput
placeholder={placeholder}
value={value}
onChange={(event) => onChange(event.currentTarget.value)}
color={getInputColor()}
error={validationState === 'invalid' ? getHelperText() : undefined}
/>
</div>
<Button
onClick={onSelectFile}
variant="light"
leftSection={<File size={16} />}
>
Browse
</Button>
{showSearchHF && (
<Button
onClick={() => {
window.electronAPI.app.openExternal(searchUrl);
}}
variant="outline"
leftSection={<Search size={16} />}
>
Search HF
</Button>
)}
</Group>
</div>
);
};

View file

@ -0,0 +1,23 @@
import { Group, Text } from '@mantine/core';
import { InfoTooltip } from '@/components/InfoTooltip';
interface SectionHeaderProps {
title: string;
tooltip?: string;
fontWeight?: number;
marginBottom?: string;
}
export const SectionHeader = ({
title,
tooltip,
fontWeight = 600,
marginBottom = 'md',
}: SectionHeaderProps) => (
<Group gap="xs" align="center" mb={marginBottom}>
<Text size="sm" fw={fontWeight}>
{title}
</Text>
{tooltip && <InfoTooltip label={tooltip} />}
</Group>
);

View file

@ -1,8 +1,8 @@
import { Stack, Checkbox, Group, Text, TextInput } from '@mantine/core';
import { Stack, Group, Text, TextInput } from '@mantine/core';
import { useState, useEffect } from 'react';
import { InfoTooltip } from '@/components/InfoTooltip';
import { CheckboxWithTooltip } from '@/components/CheckboxWithTooltip';
import { useLaunchConfig } from '@/hooks/useLaunchConfig';
import styles from '@/styles/layout.module.css';
export const AdvancedTab = () => {
const {
@ -68,101 +68,61 @@ export const AdvancedTab = () => {
</Group>
<Stack gap="md">
<Group gap="lg" align="flex-start" wrap="nowrap">
<div className={styles.minWidth200}>
<Group gap="xs" align="center">
<Checkbox
checked={!noshift}
onChange={(event) =>
handleNoshiftChange(!event.currentTarget.checked)
}
label="Context Shift"
/>
<InfoTooltip label="Use Context Shifting to reduce reprocessing." />
</Group>
</div>
<CheckboxWithTooltip
checked={!noshift}
onChange={(checked) => handleNoshiftChange(!checked)}
label="Context Shift"
tooltip="Use Context Shifting to reduce reprocessing."
/>
<div className={styles.minWidth200}>
<Group gap="xs" align="center">
<Checkbox
checked={noshift}
onChange={(event) =>
handleNoshiftChange(event.currentTarget.checked)
}
label="No Shift"
/>
<InfoTooltip label="Don't use GPU layer shifting for incomplete offloads, which may reduce model performance." />
</Group>
</div>
<CheckboxWithTooltip
checked={noshift}
onChange={handleNoshiftChange}
label="No Shift"
tooltip="Don't use GPU layer shifting for incomplete offloads, which may reduce model performance."
/>
</Group>
<Group gap="lg" align="flex-start" wrap="nowrap">
<div className={styles.minWidth200}>
<Group gap="xs" align="center">
<Checkbox
checked={flashattention}
onChange={(event) =>
handleFlashattentionChange(event.currentTarget.checked)
}
label="Flash Attention"
/>
<InfoTooltip label="Enable flash attention to reduce memory usage. May produce incorrect answers for some prompts, but improves performance." />
</Group>
</div>
<CheckboxWithTooltip
checked={flashattention}
onChange={handleFlashattentionChange}
label="Flash Attention"
tooltip="Enable flash attention to reduce memory usage. May produce incorrect answers for some prompts, but improves performance."
/>
<div className={styles.minWidth200}>
<Group gap="xs" align="center">
<Checkbox
checked={lowvram}
onChange={(event) =>
handleLowvramChange(event.currentTarget.checked)
}
label="Low VRAM"
disabled={backend !== 'cuda' && backend !== 'rocm'}
/>
<InfoTooltip
label={
backend !== 'cuda' && backend !== 'rocm'
? 'Low VRAM mode is only available for CUDA and ROCm backends.'
: 'Avoid offloading KV Cache or scratch buffers to VRAM. Allows more layers to fit, but may result in a speed loss.'
}
/>
</Group>
</div>
<CheckboxWithTooltip
checked={usemmap}
onChange={handleUsemmapChange}
label="MMAP"
tooltip="Use MMAP to load models when enabled."
/>
<CheckboxWithTooltip
checked={lowvram}
onChange={handleLowvramChange}
label="Low VRAM"
tooltip={
backend !== 'cuda' && backend !== 'rocm'
? 'Low VRAM mode is only available for CUDA and ROCm backends.'
: 'Avoid offloading KV Cache or scratch buffers to VRAM. Allows more layers to fit, but may result in a speed loss.'
}
disabled={backend !== 'cuda' && backend !== 'rocm'}
/>
</Group>
<Group gap="lg" align="flex-start" wrap="nowrap">
<div className={styles.minWidth200}>
<Group gap="xs" align="center">
<Checkbox
checked={quantmatmul}
onChange={(event) =>
handleQuantmatmulChange(event.currentTarget.checked)
}
label="QuantMatMul"
disabled={backend !== 'cuda' && backend !== 'rocm'}
/>
<InfoTooltip
label={
backend !== 'cuda' && backend !== 'rocm'
? 'QuantMatMul is only available for CUDA and ROCm backends.'
: 'Enable MMQ mode to use finetuned kernels instead of default CuBLAS/HipBLAS for prompt processing.'
}
/>
</Group>
</div>
<div className={styles.minWidth200}>
<Group gap="xs" align="center">
<Checkbox
checked={usemmap}
onChange={(event) =>
handleUsemmapChange(event.currentTarget.checked)
}
label="MMAP"
/>
<InfoTooltip label="Use MMAP to load models when enabled." />
</Group>
</div>
<CheckboxWithTooltip
checked={quantmatmul}
onChange={handleQuantmatmulChange}
label="QuantMatMul"
tooltip={
backend !== 'cuda' && backend !== 'rocm'
? 'QuantMatMul is only available for CUDA and ROCm backends.'
: 'Enable MMQ mode to use finetuned kernels instead of default CuBLAS/HipBLAS for prompt processing.'
}
disabled={backend !== 'cuda' && backend !== 'rocm'}
/>
</Group>
</Stack>
</div>
@ -175,45 +135,29 @@ export const AdvancedTab = () => {
</Group>
<Stack gap="md">
<Group gap="lg" align="flex-start" wrap="nowrap">
<div className={styles.minWidth200}>
<Group gap="xs" align="center">
<Checkbox
checked={noavx2}
onChange={(event) =>
handleNoavx2Change(event.currentTarget.checked)
}
label="Disable AVX2"
disabled={isLoading || !backendSupport?.noavx2}
/>
<InfoTooltip
label={
!backendSupport?.noavx2 && !isLoading
? 'This binary does not support the no-AVX2 mode.'
: 'Do not use AVX2 instructions, a slower compatibility mode for older devices.'
}
/>
</Group>
</div>
<CheckboxWithTooltip
checked={noavx2}
onChange={handleNoavx2Change}
label="Disable AVX2"
tooltip={
!backendSupport?.noavx2 && !isLoading
? 'This binary does not support the no-AVX2 mode.'
: 'Do not use AVX2 instructions, a slower compatibility mode for older devices.'
}
disabled={isLoading || !backendSupport?.noavx2}
/>
<div className={styles.minWidth200}>
<Group gap="xs" align="center">
<Checkbox
checked={failsafe}
onChange={(event) =>
handleFailsafeChange(event.currentTarget.checked)
}
label="Failsafe"
disabled={isLoading || !backendSupport?.failsafe}
/>
<InfoTooltip
label={
!backendSupport?.failsafe && !isLoading
? 'This binary does not support failsafe mode.'
: 'Use failsafe mode, extremely slow CPU only compatibility mode that should work on all devices. Can be combined with useclblast if your device supports OpenCL.'
}
/>
</Group>
</div>
<CheckboxWithTooltip
checked={failsafe}
onChange={handleFailsafeChange}
label="Failsafe"
tooltip={
!backendSupport?.failsafe && !isLoading
? 'This binary does not support failsafe mode.'
: 'Use failsafe mode, extremely slow CPU only compatibility mode that should work on all devices. Can be combined with useclblast if your device supports OpenCL.'
}
disabled={isLoading || !backendSupport?.failsafe}
/>
</Group>
</Stack>
</div>

View file

@ -1,10 +1,9 @@
import { Stack, Text, Group, TextInput, Button, Select } from '@mantine/core';
import { Stack, Select } from '@mantine/core';
import { useState } from 'react';
import { File, Search } from 'lucide-react';
import { InfoTooltip } from '@/components/InfoTooltip';
import { getInputValidationState, IMAGE_MODEL_PRESETS } from '@/utils';
import { SectionHeader } from '@/components/SectionHeader';
import { ModelFileField } from '@/components/ModelFileField';
import { IMAGE_MODEL_PRESETS } from '@/utils';
import { useLaunchConfig } from '@/hooks/useLaunchConfig';
import styles from '@/styles/layout.module.css';
export const ImageGenerationTab = () => {
const {
@ -34,100 +33,15 @@ export const ImageGenerationTab = () => {
const [selectedPreset, setSelectedPreset] = useState<string | null>(null);
const ModelField = ({
label,
value,
placeholder,
tooltip,
onChange,
onSelectFile,
showSearchHF = false,
}: {
label: string;
value: string;
placeholder: string;
tooltip?: string;
onChange: (value: string) => void;
onSelectFile: () => void;
showSearchHF?: boolean;
}) => {
const validationState = getInputValidationState(value);
const getInputColor = () => {
switch (validationState) {
case 'valid':
return 'green';
case 'invalid':
return 'red';
default:
return undefined;
}
};
const getHelperText = () => {
if (!value.trim()) return undefined;
if (validationState === 'invalid') {
return 'Enter a valid URL or file path';
}
return undefined;
};
return (
<div>
<Group gap="xs" align="center" mb="xs">
<Text size="sm" fw={500}>
{label}
</Text>
{tooltip && <InfoTooltip label={tooltip} />}
</Group>
<Group gap="xs" align="flex-start">
<div className={styles.flex1}>
<TextInput
placeholder={placeholder}
value={value}
onChange={(event) => onChange(event.currentTarget.value)}
color={getInputColor()}
error={
validationState === 'invalid' ? getHelperText() : undefined
}
/>
</div>
<Button
onClick={onSelectFile}
variant="light"
leftSection={<File size={16} />}
>
Browse
</Button>
{showSearchHF && (
<Button
onClick={() => {
window.electronAPI.app.openExternal(
'https://huggingface.co/models?pipeline_tag=text-to-image&library=gguf&sort=trending'
);
}}
variant="outline"
leftSection={<Search size={16} />}
>
Search HF
</Button>
)}
</Group>
</div>
);
};
return (
<Stack gap="md">
<div>
<Group gap="xs" align="center" mb="xs">
<Text size="sm" fw={500}>
Model Preset
</Text>
<InfoTooltip label="Quick presets for popular image generation models with pre-configured encoders." />
</Group>
<SectionHeader
title="Model Preset"
tooltip="Quick presets for popular image generation models with pre-configured encoders."
fontWeight={500}
marginBottom="xs"
/>
<Select
placeholder="Choose a preset..."
data={IMAGE_MODEL_PRESETS.map((preset) => ({
@ -145,40 +59,45 @@ export const ImageGenerationTab = () => {
/>
</div>
<ModelField
<ModelFileField
label="Image Gen. Model File"
value={sdmodel}
placeholder="Select a model file or enter a direct URL"
tooltip="The primary image generation model. This is the main model that will generate images."
onChange={handleSdmodelChange}
onSelectFile={handleSelectSdmodelFile}
showSearchHF
searchUrl="https://huggingface.co/models?pipeline_tag=text-to-image&library=gguf&sort=trending"
/>
<ModelField
<ModelFileField
label="T5-XXL File"
value={sdt5xxl}
placeholder="Select a T5-XXL file or enter a direct URL"
tooltip="T5-XXL text encoder model for enhanced text understanding."
onChange={handleSdt5xxlChange}
onSelectFile={handleSelectSdt5xxlFile}
/>
<ModelField
<ModelFileField
label="Clip-L File"
value={sdclipl}
placeholder="Select a Clip-L file or enter a direct URL"
tooltip="CLIP-L text encoder model for text-image understanding."
onChange={handleSdcliplChange}
onSelectFile={handleSelectSdcliplFile}
/>
<ModelField
<ModelFileField
label="Clip-G File"
value={sdclipg}
placeholder="Select a Clip-G file or enter a direct URL"
tooltip="CLIP-G text encoder model for enhanced text-image understanding."
onChange={handleSdclipgChange}
onSelectFile={handleSelectSdclipgFile}
/>
<ModelField
<ModelFileField
label="PhotoMaker"
value={sdphotomaker}
placeholder="Select a PhotoMaker file or enter a direct URL"
@ -187,15 +106,16 @@ export const ImageGenerationTab = () => {
onSelectFile={handleSelectSdphotomakerFile}
/>
<ModelField
<ModelFileField
label="Image VAE"
value={sdvae}
placeholder="Select a VAE file or enter a direct URL"
tooltip="Variational Autoencoder model for improved image quality."
onChange={handleSdvaeChange}
onSelectFile={handleSelectSdvaeFile}
/>
<ModelField
<ModelFileField
label="Image LoRa"
value={sdlora}
placeholder="Select a LoRa file or enter a direct URL"

View file

@ -1,8 +1,8 @@
import { Stack, Text, TextInput, Group, Checkbox } from '@mantine/core';
import { Stack, Text, TextInput, Group } from '@mantine/core';
import { useState, useEffect } from 'react';
import { InfoTooltip } from '@/components/InfoTooltip';
import { CheckboxWithTooltip } from '@/components/CheckboxWithTooltip';
import { useLaunchConfig } from '@/hooks/useLaunchConfig';
import styles from '@/styles/layout.module.css';
export const NetworkTab = () => {
const {
@ -85,71 +85,43 @@ export const NetworkTab = () => {
<div>
<Stack gap="md">
<Group gap="lg" align="flex-start" wrap="nowrap">
<div className={styles.minWidth200}>
<Group gap="xs" align="center">
<Checkbox
checked={multiuser}
onChange={(event) =>
handleMultiuserChange(event.currentTarget.checked)
}
label="Multiuser Mode"
/>
<InfoTooltip label="Allows requests by multiple different clients to be queued and handled in sequence." />
</Group>
</div>
<CheckboxWithTooltip
checked={multiuser}
onChange={handleMultiuserChange}
label="Multiuser Mode"
tooltip="Allows requests by multiple different clients to be queued and handled in sequence."
/>
<div className={styles.minWidth200}>
<Group gap="xs" align="center">
<Checkbox
checked={multiplayer}
onChange={(event) =>
handleMultiplayerChange(event.currentTarget.checked)
}
label="Shared Multiplayer"
/>
<InfoTooltip label="Hosts a shared multiplayer session" />
</Group>
</div>
<CheckboxWithTooltip
checked={multiplayer}
onChange={handleMultiplayerChange}
label="Shared Multiplayer"
tooltip="Hosts a shared multiplayer session"
/>
</Group>
<Group gap="lg" align="flex-start" wrap="nowrap">
<div className={styles.minWidth200}>
<Group gap="xs" align="center">
<Checkbox
checked={remotetunnel}
onChange={(event) =>
handleRemotetunnelChange(event.currentTarget.checked)
}
label="Remote Tunnel"
/>
<InfoTooltip label="Creates a trycloudflare tunnel. Allows you to access koboldcpp from other devices over an internet URL." />
</Group>
</div>
<div className={styles.minWidth200}>
<Group gap="xs" align="center">
<Checkbox
checked={nocertify}
onChange={(event) =>
handleNocertifyChange(event.currentTarget.checked)
}
label="No Certify Mode (Insecure)"
/>
<InfoTooltip label="Allows insecure SSL connections. Use this if you have SSL cert errors and need to bypass certificate restrictions." />
</Group>
</div>
</Group>
<Group gap="xs" align="center">
<Checkbox
checked={websearch}
onChange={(event) =>
handleWebsearchChange(event.currentTarget.checked)
}
label="Enable WebSearch"
<CheckboxWithTooltip
checked={remotetunnel}
onChange={handleRemotetunnelChange}
label="Remote Tunnel"
tooltip="Creates a trycloudflare tunnel. Allows you to access koboldcpp from other devices over an internet URL."
/>
<CheckboxWithTooltip
checked={nocertify}
onChange={handleNocertifyChange}
label="No Certify Mode (Insecure)"
tooltip="Allows insecure SSL connections. Use this if you have SSL cert errors and need to bypass certificate restrictions."
/>
<InfoTooltip label="Enable the local search engine proxy so Web Searches can be done." />
</Group>
<CheckboxWithTooltip
checked={websearch}
onChange={handleWebsearchChange}
label="Enable WebSearch"
tooltip="Enable the local search engine proxy so Web Searches can be done."
/>
</Stack>
</div>
</Stack>