diff --git a/README.md b/README.md
index 275d0ac..45f5472 100644
--- a/README.md
+++ b/README.md
@@ -42,7 +42,9 @@ A koboldcpp manager.
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
diff --git a/eslint.config.ts b/eslint.config.ts
index efe731e..35f9871 100644
--- a/eslint.config.ts
+++ b/eslint.config.ts
@@ -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],
diff --git a/package.json b/package.json
index dcbe4aa..01ca5bf 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/src/App.tsx b/src/App.tsx
index 9196515..6bf26c6 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -241,7 +241,7 @@ export const App = () => {
}}
>
{currentScreen === null ? (
-
+
diff --git a/src/components/AppHeader.tsx b/src/components/AppHeader.tsx
index c9b8f7b..67dac25 100644
--- a/src/components/AppHeader.tsx
+++ b/src/components/AppHeader.tsx
@@ -66,7 +66,7 @@ export const AppHeader = ({
return (
-
+
{currentScreen === 'interface' ? (
@@ -136,7 +136,7 @@ export const AppHeader = ({
-
+
{
label="MMAP"
tooltip="Use MMAP to load models when enabled."
/>
-
-
@@ -123,6 +111,18 @@ export const AdvancedTab = () => {
}
disabled={backend !== 'cuda' && backend !== 'rocm'}
/>
+
+
diff --git a/src/components/screens/Launch/ConfigFileManager.tsx b/src/components/screens/Launch/ConfigFileManager.tsx
index 320bd8e..058e021 100644
--- a/src/components/screens/Launch/ConfigFileManager.tsx
+++ b/src/components/screens/Launch/ConfigFileManager.tsx
@@ -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
;
onCreateNewConfig: (configName: string) => Promise;
- onSaveConfig: () => void;
+ onSaveConfig: () => Promise;
onLoadConfigFiles: () => Promise;
}
@@ -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 = ({
}
+ leftSection={saveSuccess ? : }
size="sm"
- onClick={() => {
- if (selectedFile) {
- onSaveConfig();
- } else {
- handleOpenConfigModal();
- }
- }}
+ onClick={handleSaveClick}
+ color={saveSuccess ? 'green' : undefined}
+ style={{ width: '6rem' }}
>
- Save
+ {saveSuccess ? 'Saved!' : 'Save'}
diff --git a/src/components/screens/Launch/index.tsx b/src/components/screens/Launch/index.tsx
index 961c7b2..fd65056 100644
--- a/src/components/screens/Launch/index.tsx
+++ b/src/components/screens/Launch/index.tsx
@@ -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;
}
};
diff --git a/src/components/settings/VersionsTab.tsx b/src/components/settings/VersionsTab.tsx
index ff087a1..01e2720 100644
--- a/src/components/settings/VersionsTab.tsx
+++ b/src/components/settings/VersionsTab.tsx
@@ -347,7 +347,7 @@ export const VersionsTab = () => {
return (
((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 {
diff --git a/src/styles/index.css b/src/styles/index.css
index c57a5b4..1bb3633 100644
--- a/src/styles/index.css
+++ b/src/styles/index.css
@@ -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);
diff --git a/src/styles/layout.module.css b/src/styles/layout.module.css
index a0e4401..468eb7a 100644
--- a/src/styles/layout.module.css
+++ b/src/styles/layout.module.css
@@ -1,5 +1,5 @@
.minWidth200 {
- min-width: 200px;
+ min-width: 12.5rem;
}
.flex1 {
diff --git a/src/utils/sounds.ts b/src/utils/sounds.ts
index ea45abd..5f95e75 100644
--- a/src/utils/sounds.ts
+++ b/src/utils/sounds.ts
@@ -19,7 +19,7 @@ export const soundAssets = {
const audioCache = new Map();
let audioInitialized = false;
-export const initializeAudio = async (): Promise => {
+export const initializeAudio = async () => {
if (audioInitialized) return;
try {
@@ -50,10 +50,7 @@ export const initializeAudio = async (): Promise => {
}
};
-export const playSound = async (
- soundUrl: string,
- volume = 0.5
-): Promise => {
+export const playSound = async (soundUrl: string, volume = 0.5) => {
try {
if (!audioInitialized) {
await initializeAudio();
diff --git a/yarn.lock b/yarn.lock
index 50ce7d6..240d4b1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -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