refactoring out duplicate code

This commit is contained in:
lone-cloud 2025-08-21 16:14:01 -07:00
parent 2d34cf7a8d
commit a0d1599b9d
7 changed files with 290 additions and 309 deletions

View file

@ -1,44 +1,40 @@
AMDGPU AMDGPU
APPIMAGE APPIMAGE
BLAS
CUDA
GGUF
KOBOLDAI
KOBOLDCPP
OLDPC
ROCM
SDXL
SPACEBAR
Consolas
Cooldown
Dolfino
Flashattention
KoboldCpp
Lowvram
Segoe
Tauri
asar asar
Autoencoder
BLAS
clblast clblast
clinfo clinfo
Consolas
contextsize contextsize
Cooldown
cublas cublas
cuda cuda
CUDA
Dolfino
finetuned finetuned
flashattention flashattention
Flashattention
friendlykobold friendlykobold
geforce geforce
ggml ggml
gguf gguf
GGUF
gpulayers gpulayers
hipblas hipblas
kcpps kcpps
kcppt kcppt
koboldai koboldai
KOBOLDAI
koboldcpp koboldcpp
KoboldCpp
KOBOLDCPP
lora lora
lowvram lowvram
Lowvram
maximizable maximizable
minimizable minimizable
MMAP
mmproj mmproj
mmq mmq
multiuser multiuser
@ -51,12 +47,14 @@ noshift
nsis nsis
nvidia nvidia
oldpc oldpc
OLDPC
opencl opencl
pkexec pkexec
quantmatmul quantmatmul
radeon radeon
remotetunnel remotetunnel
rocm rocm
ROCM
rocminfo rocminfo
safetensors safetensors
sdclipg sdclipg
@ -66,8 +64,12 @@ sdmodel
sdphotomaker sdphotomaker
sdui sdui
sdvae sdvae
SDXL
Segoe
sonarjs sonarjs
SPACEBAR
taskkill taskkill
Tauri
togglefullscreen togglefullscreen
treemap treemap
trycloudflare trycloudflare
@ -81,4 +83,3 @@ vulkan
vulkaninfo vulkaninfo
wayland wayland
websearch 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 { useState, useEffect } from 'react';
import { InfoTooltip } from '@/components/InfoTooltip'; import { InfoTooltip } from '@/components/InfoTooltip';
import { CheckboxWithTooltip } from '@/components/CheckboxWithTooltip';
import { useLaunchConfig } from '@/hooks/useLaunchConfig'; import { useLaunchConfig } from '@/hooks/useLaunchConfig';
import styles from '@/styles/layout.module.css';
export const AdvancedTab = () => { export const AdvancedTab = () => {
const { const {
@ -68,102 +68,62 @@ export const AdvancedTab = () => {
</Group> </Group>
<Stack gap="md"> <Stack gap="md">
<Group gap="lg" align="flex-start" wrap="nowrap"> <Group gap="lg" align="flex-start" wrap="nowrap">
<div className={styles.minWidth200}> <CheckboxWithTooltip
<Group gap="xs" align="center">
<Checkbox
checked={!noshift} checked={!noshift}
onChange={(event) => onChange={(checked) => handleNoshiftChange(!checked)}
handleNoshiftChange(!event.currentTarget.checked)
}
label="Context Shift" label="Context Shift"
tooltip="Use Context Shifting to reduce reprocessing."
/> />
<InfoTooltip label="Use Context Shifting to reduce reprocessing." />
</Group>
</div>
<div className={styles.minWidth200}> <CheckboxWithTooltip
<Group gap="xs" align="center">
<Checkbox
checked={noshift} checked={noshift}
onChange={(event) => onChange={handleNoshiftChange}
handleNoshiftChange(event.currentTarget.checked)
}
label="No Shift" label="No Shift"
tooltip="Don't use GPU layer shifting for incomplete offloads, which may reduce model performance."
/> />
<InfoTooltip label="Don't use GPU layer shifting for incomplete offloads, which may reduce model performance." />
</Group>
</div>
</Group> </Group>
<Group gap="lg" align="flex-start" wrap="nowrap"> <Group gap="lg" align="flex-start" wrap="nowrap">
<div className={styles.minWidth200}> <CheckboxWithTooltip
<Group gap="xs" align="center">
<Checkbox
checked={flashattention} checked={flashattention}
onChange={(event) => onChange={handleFlashattentionChange}
handleFlashattentionChange(event.currentTarget.checked)
}
label="Flash Attention" label="Flash Attention"
tooltip="Enable flash attention to reduce memory usage. May produce incorrect answers for some prompts, but improves performance."
/> />
<InfoTooltip label="Enable flash attention to reduce memory usage. May produce incorrect answers for some prompts, but improves performance." />
</Group>
</div>
<div className={styles.minWidth200}> <CheckboxWithTooltip
<Group gap="xs" align="center"> checked={usemmap}
<Checkbox onChange={handleUsemmapChange}
checked={lowvram} label="MMAP"
onChange={(event) => tooltip="Use MMAP to load models when enabled."
handleLowvramChange(event.currentTarget.checked)
}
label="Low VRAM"
disabled={backend !== 'cuda' && backend !== 'rocm'}
/> />
<InfoTooltip
label={ <CheckboxWithTooltip
checked={lowvram}
onChange={handleLowvramChange}
label="Low VRAM"
tooltip={
backend !== 'cuda' && backend !== 'rocm' backend !== 'cuda' && backend !== 'rocm'
? 'Low VRAM mode is only available for CUDA and ROCm backends.' ? '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.' : '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>
</div>
</Group>
<Group gap="lg" align="flex-start" wrap="nowrap"> <Group gap="lg" align="flex-start" wrap="nowrap">
<div className={styles.minWidth200}> <CheckboxWithTooltip
<Group gap="xs" align="center">
<Checkbox
checked={quantmatmul} checked={quantmatmul}
onChange={(event) => onChange={handleQuantmatmulChange}
handleQuantmatmulChange(event.currentTarget.checked)
}
label="QuantMatMul" label="QuantMatMul"
disabled={backend !== 'cuda' && backend !== 'rocm'} tooltip={
/>
<InfoTooltip
label={
backend !== 'cuda' && backend !== 'rocm' backend !== 'cuda' && backend !== 'rocm'
? 'QuantMatMul is only available for CUDA and ROCm backends.' ? 'QuantMatMul is only available for CUDA and ROCm backends.'
: 'Enable MMQ mode to use finetuned kernels instead of default CuBLAS/HipBLAS for prompt processing.' : 'Enable MMQ mode to use finetuned kernels instead of default CuBLAS/HipBLAS for prompt processing.'
} }
disabled={backend !== 'cuda' && backend !== 'rocm'}
/> />
</Group> </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>
</Group>
</Stack> </Stack>
</div> </div>
@ -175,46 +135,30 @@ export const AdvancedTab = () => {
</Group> </Group>
<Stack gap="md"> <Stack gap="md">
<Group gap="lg" align="flex-start" wrap="nowrap"> <Group gap="lg" align="flex-start" wrap="nowrap">
<div className={styles.minWidth200}> <CheckboxWithTooltip
<Group gap="xs" align="center">
<Checkbox
checked={noavx2} checked={noavx2}
onChange={(event) => onChange={handleNoavx2Change}
handleNoavx2Change(event.currentTarget.checked)
}
label="Disable AVX2" label="Disable AVX2"
disabled={isLoading || !backendSupport?.noavx2} tooltip={
/>
<InfoTooltip
label={
!backendSupport?.noavx2 && !isLoading !backendSupport?.noavx2 && !isLoading
? 'This binary does not support the no-AVX2 mode.' ? 'This binary does not support the no-AVX2 mode.'
: 'Do not use AVX2 instructions, a slower compatibility mode for older devices.' : 'Do not use AVX2 instructions, a slower compatibility mode for older devices.'
} }
disabled={isLoading || !backendSupport?.noavx2}
/> />
</Group>
</div>
<div className={styles.minWidth200}> <CheckboxWithTooltip
<Group gap="xs" align="center">
<Checkbox
checked={failsafe} checked={failsafe}
onChange={(event) => onChange={handleFailsafeChange}
handleFailsafeChange(event.currentTarget.checked)
}
label="Failsafe" label="Failsafe"
disabled={isLoading || !backendSupport?.failsafe} tooltip={
/>
<InfoTooltip
label={
!backendSupport?.failsafe && !isLoading !backendSupport?.failsafe && !isLoading
? 'This binary does not support failsafe mode.' ? '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.' : '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> </Group>
</div>
</Group>
</Stack> </Stack>
</div> </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 { useState } from 'react';
import { File, Search } from 'lucide-react'; import { SectionHeader } from '@/components/SectionHeader';
import { InfoTooltip } from '@/components/InfoTooltip'; import { ModelFileField } from '@/components/ModelFileField';
import { getInputValidationState, IMAGE_MODEL_PRESETS } from '@/utils'; import { IMAGE_MODEL_PRESETS } from '@/utils';
import { useLaunchConfig } from '@/hooks/useLaunchConfig'; import { useLaunchConfig } from '@/hooks/useLaunchConfig';
import styles from '@/styles/layout.module.css';
export const ImageGenerationTab = () => { export const ImageGenerationTab = () => {
const { const {
@ -34,100 +33,15 @@ export const ImageGenerationTab = () => {
const [selectedPreset, setSelectedPreset] = useState<string | null>(null); 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 ( return (
<Stack gap="md"> <Stack gap="md">
<div> <div>
<Group gap="xs" align="center" mb="xs"> <SectionHeader
<Text size="sm" fw={500}> title="Model Preset"
Model Preset tooltip="Quick presets for popular image generation models with pre-configured encoders."
</Text> fontWeight={500}
<InfoTooltip label="Quick presets for popular image generation models with pre-configured encoders." /> marginBottom="xs"
</Group> />
<Select <Select
placeholder="Choose a preset..." placeholder="Choose a preset..."
data={IMAGE_MODEL_PRESETS.map((preset) => ({ data={IMAGE_MODEL_PRESETS.map((preset) => ({
@ -145,40 +59,45 @@ export const ImageGenerationTab = () => {
/> />
</div> </div>
<ModelField <ModelFileField
label="Image Gen. Model File" label="Image Gen. Model File"
value={sdmodel} value={sdmodel}
placeholder="Select a model file or enter a direct URL" 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} onChange={handleSdmodelChange}
onSelectFile={handleSelectSdmodelFile} onSelectFile={handleSelectSdmodelFile}
showSearchHF showSearchHF
searchUrl="https://huggingface.co/models?pipeline_tag=text-to-image&library=gguf&sort=trending"
/> />
<ModelField <ModelFileField
label="T5-XXL File" label="T5-XXL File"
value={sdt5xxl} value={sdt5xxl}
placeholder="Select a T5-XXL file or enter a direct URL" placeholder="Select a T5-XXL file or enter a direct URL"
tooltip="T5-XXL text encoder model for enhanced text understanding."
onChange={handleSdt5xxlChange} onChange={handleSdt5xxlChange}
onSelectFile={handleSelectSdt5xxlFile} onSelectFile={handleSelectSdt5xxlFile}
/> />
<ModelField <ModelFileField
label="Clip-L File" label="Clip-L File"
value={sdclipl} value={sdclipl}
placeholder="Select a Clip-L file or enter a direct URL" placeholder="Select a Clip-L file or enter a direct URL"
tooltip="CLIP-L text encoder model for text-image understanding."
onChange={handleSdcliplChange} onChange={handleSdcliplChange}
onSelectFile={handleSelectSdcliplFile} onSelectFile={handleSelectSdcliplFile}
/> />
<ModelField <ModelFileField
label="Clip-G File" label="Clip-G File"
value={sdclipg} value={sdclipg}
placeholder="Select a Clip-G file or enter a direct URL" placeholder="Select a Clip-G file or enter a direct URL"
tooltip="CLIP-G text encoder model for enhanced text-image understanding."
onChange={handleSdclipgChange} onChange={handleSdclipgChange}
onSelectFile={handleSelectSdclipgFile} onSelectFile={handleSelectSdclipgFile}
/> />
<ModelField <ModelFileField
label="PhotoMaker" label="PhotoMaker"
value={sdphotomaker} value={sdphotomaker}
placeholder="Select a PhotoMaker file or enter a direct URL" placeholder="Select a PhotoMaker file or enter a direct URL"
@ -187,15 +106,16 @@ export const ImageGenerationTab = () => {
onSelectFile={handleSelectSdphotomakerFile} onSelectFile={handleSelectSdphotomakerFile}
/> />
<ModelField <ModelFileField
label="Image VAE" label="Image VAE"
value={sdvae} value={sdvae}
placeholder="Select a VAE file or enter a direct URL" placeholder="Select a VAE file or enter a direct URL"
tooltip="Variational Autoencoder model for improved image quality."
onChange={handleSdvaeChange} onChange={handleSdvaeChange}
onSelectFile={handleSelectSdvaeFile} onSelectFile={handleSelectSdvaeFile}
/> />
<ModelField <ModelFileField
label="Image LoRa" label="Image LoRa"
value={sdlora} value={sdlora}
placeholder="Select a LoRa file or enter a direct URL" 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 { useState, useEffect } from 'react';
import { InfoTooltip } from '@/components/InfoTooltip'; import { InfoTooltip } from '@/components/InfoTooltip';
import { CheckboxWithTooltip } from '@/components/CheckboxWithTooltip';
import { useLaunchConfig } from '@/hooks/useLaunchConfig'; import { useLaunchConfig } from '@/hooks/useLaunchConfig';
import styles from '@/styles/layout.module.css';
export const NetworkTab = () => { export const NetworkTab = () => {
const { const {
@ -85,71 +85,43 @@ export const NetworkTab = () => {
<div> <div>
<Stack gap="md"> <Stack gap="md">
<Group gap="lg" align="flex-start" wrap="nowrap"> <Group gap="lg" align="flex-start" wrap="nowrap">
<div className={styles.minWidth200}> <CheckboxWithTooltip
<Group gap="xs" align="center">
<Checkbox
checked={multiuser} checked={multiuser}
onChange={(event) => onChange={handleMultiuserChange}
handleMultiuserChange(event.currentTarget.checked)
}
label="Multiuser Mode" label="Multiuser Mode"
tooltip="Allows requests by multiple different clients to be queued and handled in sequence."
/> />
<InfoTooltip label="Allows requests by multiple different clients to be queued and handled in sequence." />
</Group>
</div>
<div className={styles.minWidth200}> <CheckboxWithTooltip
<Group gap="xs" align="center">
<Checkbox
checked={multiplayer} checked={multiplayer}
onChange={(event) => onChange={handleMultiplayerChange}
handleMultiplayerChange(event.currentTarget.checked)
}
label="Shared Multiplayer" label="Shared Multiplayer"
tooltip="Hosts a shared multiplayer session"
/> />
<InfoTooltip label="Hosts a shared multiplayer session" />
</Group>
</div>
</Group> </Group>
<Group gap="lg" align="flex-start" wrap="nowrap"> <Group gap="lg" align="flex-start" wrap="nowrap">
<div className={styles.minWidth200}> <CheckboxWithTooltip
<Group gap="xs" align="center">
<Checkbox
checked={remotetunnel} checked={remotetunnel}
onChange={(event) => onChange={handleRemotetunnelChange}
handleRemotetunnelChange(event.currentTarget.checked)
}
label="Remote Tunnel" label="Remote Tunnel"
tooltip="Creates a trycloudflare tunnel. Allows you to access koboldcpp from other devices over an internet URL."
/> />
<InfoTooltip label="Creates a trycloudflare tunnel. Allows you to access koboldcpp from other devices over an internet URL." />
</Group>
</div>
<div className={styles.minWidth200}> <CheckboxWithTooltip
<Group gap="xs" align="center">
<Checkbox
checked={nocertify} checked={nocertify}
onChange={(event) => onChange={handleNocertifyChange}
handleNocertifyChange(event.currentTarget.checked)
}
label="No Certify Mode (Insecure)" 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="Allows insecure SSL connections. Use this if you have SSL cert errors and need to bypass certificate restrictions." />
</Group>
</div>
</Group> </Group>
<Group gap="xs" align="center"> <CheckboxWithTooltip
<Checkbox
checked={websearch} checked={websearch}
onChange={(event) => onChange={handleWebsearchChange}
handleWebsearchChange(event.currentTarget.checked)
}
label="Enable WebSearch" label="Enable WebSearch"
tooltip="Enable the local search engine proxy so Web Searches can be done."
/> />
<InfoTooltip label="Enable the local search engine proxy so Web Searches can be done." />
</Group>
</Stack> </Stack>
</div> </div>
</Stack> </Stack>