persist additional arguments to config, change px to rem where it makes sense

This commit is contained in:
Egor 2025-08-22 11:32:40 -07:00
parent d298203338
commit e8ccc68875
15 changed files with 93 additions and 74 deletions

View file

@ -42,7 +42,9 @@ A koboldcpp manager. <!-- markdownlint-disable MD033 -->
There is ROCm Windows support maintained by YellowRoseCx in a separate fork.
Unfortunately it does not properly support unpacking, which would greatly diminish its performance and provide a poor UX when used alongside this app.
For Friendly Kobold to work with this fork, this issue must be fixed first: https://github.com/YellowRoseCx/koboldcpp-rocm/issues/129
For Friendly Kobold to work with this fork, [this issue must be fixed first](https://github.com/YellowRoseCx/koboldcpp-rocm/issues/129).
Note that this build is not important as modern day Vulkan matches or even surpasses ROCm in terms of LLM performance for most cases.
### Future features

View file

@ -112,6 +112,7 @@ const config = [
'@typescript-eslint/no-inferrable-types': 'warn',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'sonarjs/cognitive-complexity': ['warn', 25],

View file

@ -1,7 +1,7 @@
{
"name": "friendly-kobold",
"productName": "Friendly Kobold",
"version": "0.5.0",
"version": "0.5.1",
"description": "A modern Electron shell for KoboldCpp",
"main": "out/main/index.js",
"homepage": "./",
@ -52,7 +52,7 @@
"@cspell/eslint-plugin": "^9.2.0",
"@eslint/js": "^9.33.0",
"@types/node": "^24.3.0",
"@types/react": "^19.1.10",
"@types/react": "^19.1.11",
"@types/react-dom": "^19.1.7",
"@typescript-eslint/eslint-plugin": "^8.40.0",
"@typescript-eslint/parser": "^8.40.0",
@ -79,12 +79,12 @@
"vite": "^7.1.3"
},
"dependencies": {
"@mantine/core": "^8.2.5",
"@mantine/hooks": "^8.2.5",
"@mantine/core": "^8.2.7",
"@mantine/hooks": "^8.2.7",
"ansi-to-html": "^0.7.2",
"execa": "^9.6.0",
"got": "^14.4.7",
"lucide-react": "^0.540.0",
"lucide-react": "^0.541.0",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"systeminformation": "^5.27.7",

View file

@ -241,7 +241,7 @@ export const App = () => {
}}
>
{currentScreen === null ? (
<Center h="100%" style={{ minHeight: '400px' }}>
<Center h="100%" style={{ minHeight: '25rem' }}>
<Stack align="center" gap="lg">
<Loader size="xl" type="dots" />
<Text c="dimmed" size="lg">

View file

@ -66,7 +66,7 @@ export const AppHeader = ({
return (
<AppShell.Header>
<Group h="100%" px="md" justify="space-between" align="center">
<div style={{ minWidth: '100px' }}>
<div style={{ minWidth: '6.25rem' }}>
{currentScreen === 'interface' ? (
<Button
variant="light"
@ -121,14 +121,14 @@ export const AppHeader = ({
placeholder="Select view"
styles={{
input: {
minWidth: '150px',
minWidth: '9.375rem',
textAlign: 'center',
border: 'none',
backgroundColor: 'transparent',
fontWeight: 500,
},
dropdown: {
minWidth: '150px',
minWidth: '9.375rem',
},
}}
/>
@ -136,7 +136,7 @@ export const AppHeader = ({
<div
style={{
minWidth: '100px',
minWidth: '6.25rem',
display: 'flex',
justifyContent: 'flex-end',
}}

View file

@ -113,7 +113,7 @@ export const UpdateAvailableModal = ({
</Stack>
</Card>
<Stack gap="xs" style={{ minHeight: '44px' }}>
<Stack gap="xs" style={{ minHeight: '2.75rem' }}>
<Progress
value={isDownloading ? Math.min(downloadProgress, 100) : 0}
color="orange"

View file

@ -97,18 +97,6 @@ export const AdvancedTab = () => {
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">
@ -123,6 +111,18 @@ export const AdvancedTab = () => {
}
disabled={backend !== 'cuda' && backend !== 'rocm'}
/>
<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>
</Stack>
</div>

View file

@ -14,7 +14,7 @@ import {
forwardRef,
type ComponentPropsWithoutRef,
} from 'react';
import { Save, File, Plus } from 'lucide-react';
import { Save, File, Plus, Check } from 'lucide-react';
import type { ConfigFile } from '@/types';
import styles from '@/styles/layout.module.css';
@ -23,7 +23,7 @@ interface ConfigFileManagerProps {
selectedFile: string | null;
onFileSelection: (fileName: string) => Promise<void>;
onCreateNewConfig: (configName: string) => Promise<void>;
onSaveConfig: () => void;
onSaveConfig: () => Promise<boolean>;
onLoadConfigFiles: () => Promise<void>;
}
@ -69,6 +69,7 @@ export const ConfigFileManager = ({
}: ConfigFileManagerProps) => {
const [configModalOpened, setConfigModalOpened] = useState(false);
const [newConfigName, setNewConfigName] = useState('');
const [saveSuccess, setSaveSuccess] = useState(false);
const existingConfigNames = configFiles.map((file) => {
const extension = file.name.split('.').pop() || '';
@ -89,11 +90,22 @@ export const ConfigFileManager = ({
setNewConfigName('');
}, []);
const handleConfigSubmit = useCallback(() => {
onCreateNewConfig(newConfigName.trim());
setConfigModalOpened(false);
setNewConfigName('');
}, [newConfigName, onCreateNewConfig]);
const handleConfigSubmit = async () => {
await onCreateNewConfig(trimmedConfigName);
handleCloseConfigModal();
};
const handleSaveClick = async () => {
if (selectedFile) {
const success = await onSaveConfig();
if (success) {
setSaveSuccess(true);
setTimeout(() => setSaveSuccess(false), 1500);
}
} else {
handleOpenConfigModal();
}
};
const selectData = configFiles.map((file) => {
const extension = file.name.split('.').pop() || '';
@ -151,17 +163,13 @@ export const ConfigFileManager = ({
<Button
variant="outline"
leftSection={<Save size={14} />}
leftSection={saveSuccess ? <Check size={14} /> : <Save size={14} />}
size="sm"
onClick={() => {
if (selectedFile) {
onSaveConfig();
} else {
handleOpenConfigModal();
}
}}
onClick={handleSaveClick}
color={saveSuccess ? 'green' : undefined}
style={{ width: '6rem' }}
>
Save
{saveSuccess ? 'Saved!' : 'Save'}
</Button>
</Group>
</Stack>

View file

@ -138,6 +138,7 @@ export const LaunchScreen = ({
gpulayers: gpuLayers,
contextsize: contextSize,
model: modelPath,
additionalArguments,
port,
host,
multiuser: multiuser ? 1 : 0,
@ -197,7 +198,7 @@ export const LaunchScreen = ({
'No configuration file selected for saving',
new Error('Selected file is null')
);
return;
return false;
}
try {
@ -213,12 +214,16 @@ export const LaunchScreen = ({
'Failed to save configuration',
new Error('Save operation failed')
);
return false;
}
return true;
} catch (error) {
window.electronAPI.logs.logError(
'Failed to save configuration:',
error as Error
);
return false;
}
};

View file

@ -347,7 +347,7 @@ export const VersionsTab = () => {
return (
<div
key={`${version.name}-${version.version}-${index}`}
style={{ paddingBottom: '8px' }}
style={{ paddingBottom: '0.5rem' }}
ref={isDownloading ? downloadingItemRef : null}
>
<DownloadCard

View file

@ -166,6 +166,12 @@ export const useLaunchConfigStore = create<LaunchConfigState>((set, get) => ({
updates.modelPath = configData.model;
}
if (typeof configData.additionalArguments === 'string') {
updates.additionalArguments = configData.additionalArguments;
} else {
updates.additionalArguments = '';
}
if (typeof configData.port === 'number') {
updates.port = configData.port;
} else {

View file

@ -11,18 +11,18 @@ html {
/* Custom scrollbars */
::-webkit-scrollbar {
width: 8px;
height: 8px;
width: 0.5rem;
height: 0.5rem;
}
::-webkit-scrollbar-track {
background: transparent;
border-radius: 4px;
border-radius: 0.25rem;
}
::-webkit-scrollbar-thumb {
background: rgba(134, 142, 150, 0.5);
border-radius: 4px;
border-radius: 0.25rem;
transition: all 0.2s ease;
}
@ -53,7 +53,7 @@ html {
@keyframes elephantShake {
0%,
100% {
transform: scale(1.3) rotate(5deg) translateX(0px);
transform: scale(1.3) rotate(5deg) translateX(0);
}
10% {
transform: scale(1.4) rotate(-3deg) translateX(-2px);

View file

@ -1,5 +1,5 @@
.minWidth200 {
min-width: 200px;
min-width: 12.5rem;
}
.flex1 {

View file

@ -19,7 +19,7 @@ export const soundAssets = {
const audioCache = new Map<string, HTMLAudioElement>();
let audioInitialized = false;
export const initializeAudio = async (): Promise<void> => {
export const initializeAudio = async () => {
if (audioInitialized) return;
try {
@ -50,10 +50,7 @@ export const initializeAudio = async (): Promise<void> => {
}
};
export const playSound = async (
soundUrl: string,
volume = 0.5
): Promise<void> => {
export const playSound = async (soundUrl: string, volume = 0.5) => {
try {
if (!audioInitialized) {
await initializeAudio();

View file

@ -1436,9 +1436,9 @@ __metadata:
languageName: node
linkType: hard
"@mantine/core@npm:^8.2.5":
version: 8.2.5
resolution: "@mantine/core@npm:8.2.5"
"@mantine/core@npm:^8.2.7":
version: 8.2.7
resolution: "@mantine/core@npm:8.2.7"
dependencies:
"@floating-ui/react": "npm:^0.26.28"
clsx: "npm:^2.1.1"
@ -1447,19 +1447,19 @@ __metadata:
react-textarea-autosize: "npm:8.5.9"
type-fest: "npm:^4.27.0"
peerDependencies:
"@mantine/hooks": 8.2.5
"@mantine/hooks": 8.2.7
react: ^18.x || ^19.x
react-dom: ^18.x || ^19.x
checksum: 10c0/2268218faa02ecfb07819f6fb73426bf32049a3769ee6b1d1be46bdd3fb913ff14518692213a762a1ceccf6245572a5600052572f50b02c201e388475427657a
checksum: 10c0/d10ba6ef7ac552b6bd5638b9a16c4d2405391102330d94c8df7a7d87756cdf4ec341a350eab252575eb5990f43dc4122ea13244753a9169338a43d2b996f7594
languageName: node
linkType: hard
"@mantine/hooks@npm:^8.2.5":
version: 8.2.5
resolution: "@mantine/hooks@npm:8.2.5"
"@mantine/hooks@npm:^8.2.7":
version: 8.2.7
resolution: "@mantine/hooks@npm:8.2.7"
peerDependencies:
react: ^18.x || ^19.x
checksum: 10c0/acd6d56703b19032ecced897c26dfb5ee7ff630c51cccfcaa2a9fa3251607432be0e984c8f8715456b4fa0c6647d48b4c240aa72d5eed78dbc358ec445b1f791
checksum: 10c0/54dfbc36acad9dbb5e3a29d0944041f7ff31e61416cbd26ecf97c37c3a779a1b0028e05a0e6daa44cf3c3c2406c50b8f2107eb6fee3cfb6647b7524965556686
languageName: node
linkType: hard
@ -1905,12 +1905,12 @@ __metadata:
languageName: node
linkType: hard
"@types/react@npm:^19.1.10":
version: 19.1.10
resolution: "@types/react@npm:19.1.10"
"@types/react@npm:^19.1.11":
version: 19.1.11
resolution: "@types/react@npm:19.1.11"
dependencies:
csstype: "npm:^3.0.2"
checksum: 10c0/fb583deacd0a815e2775dc1b9f764532d8cacb748ddd2c2914805a46c257ce6c237b4078f44009692074db212ab61a390301c6470f07f5aa5bfdeb78a2acfda1
checksum: 10c0/639b225c2bbcd4b8a30e1ea7a73aec81ae5b952a4c432460b48c9881c9d12e76645c9032d24f15eefae9985a12d5cb26557fe10e9850b2da0fabfb0a1e2d16bd
languageName: node
linkType: hard
@ -4401,10 +4401,10 @@ __metadata:
dependencies:
"@cspell/eslint-plugin": "npm:^9.2.0"
"@eslint/js": "npm:^9.33.0"
"@mantine/core": "npm:^8.2.5"
"@mantine/hooks": "npm:^8.2.5"
"@mantine/core": "npm:^8.2.7"
"@mantine/hooks": "npm:^8.2.7"
"@types/node": "npm:^24.3.0"
"@types/react": "npm:^19.1.10"
"@types/react": "npm:^19.1.11"
"@types/react-dom": "npm:^19.1.7"
"@typescript-eslint/eslint-plugin": "npm:^8.40.0"
"@typescript-eslint/parser": "npm:^8.40.0"
@ -4428,7 +4428,7 @@ __metadata:
husky: "npm:^9.1.7"
jiti: "npm:^2.5.1"
lint-staged: "npm:^16.1.5"
lucide-react: "npm:^0.540.0"
lucide-react: "npm:^0.541.0"
prettier: "npm:^3.6.2"
react: "npm:^19.1.1"
react-dom: "npm:^19.1.1"
@ -5828,12 +5828,12 @@ __metadata:
languageName: node
linkType: hard
"lucide-react@npm:^0.540.0":
version: 0.540.0
resolution: "lucide-react@npm:0.540.0"
"lucide-react@npm:^0.541.0":
version: 0.541.0
resolution: "lucide-react@npm:0.541.0"
peerDependencies:
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
checksum: 10c0/f4dc8a540b1b079958fdf7a1804dfb3f9093a9393f0c0a4b32a7e839869df5e29946c4fe226f06edd3056f6eccfa77b53e44be89f42379e7fe31c4e59caee3fa
checksum: 10c0/ffa23a9c9ead0832c7e0bb1eb7ed44e9f38bbf083d94b4d36a66eb23fa2099d4a52e1677c91eb7e8ea922621fa98e730f5d513bc56b3111b14b835bd8ab823ab
languageName: node
linkType: hard