diff --git a/README.md b/README.md index 8ca2a00..326acec 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,13 @@ A koboldcpp manager. +## Core Features + +- modern UI with full support for Linux Wayland +- download and keep up-to-date your [koboldcpp](https://github.com/LostRuins/koboldcpp/releases) binary +- better surface the ROCm-specific builds of koboldcpp from YellowRoseCx and from [koboldai.org](https://koboldai.org/cpplinuxrocm) +- manage the koboldcpp binary to prevent it from running in the background indefinitely + ### Prerequisites - Node.js 18+ diff --git a/cspell.json b/cspell.json index 5d31d49..620fd76 100644 --- a/cspell.json +++ b/cspell.json @@ -63,6 +63,8 @@ "flexbox", "flexdir", "flexwrap", + "flashattention", + "Flashattention", "fontsize", "fontweight", "forwardRef", @@ -125,6 +127,8 @@ "nocuda", "nodeIntegration", "noheader", + "noshift", + "Noshift", "nsis", "nvidia", "oldpc", @@ -210,6 +214,9 @@ "useReducer", "useRef", "useState", + "useclblast", + "usecuda", + "usevulkan", "util", "utils", "var", diff --git a/eslint.config.ts b/eslint.config.ts index eb0fbab..2439f8b 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -8,6 +8,7 @@ import react from 'eslint-plugin-react'; import importPlugin from 'eslint-plugin-import'; import sonarjs from 'eslint-plugin-sonarjs'; import cspell from '@cspell/eslint-plugin'; +import noComments from 'eslint-plugin-no-comments'; const config = [ js.configs.recommended, @@ -39,6 +40,7 @@ const config = [ import: importPlugin, sonarjs: sonarjs, '@cspell': cspell, + 'no-comments': noComments, }, settings: { react: { @@ -46,11 +48,9 @@ const config = [ }, }, rules: { - // Use recommended rules from plugins ...reactHooks.configs.recommended.rules, ...react.configs.recommended.rules, - // Essential TypeScript rules '@typescript-eslint/no-unused-vars': [ 'error', { @@ -59,10 +59,9 @@ const config = [ ignoreRestSiblings: true, }, ], - 'no-unused-vars': 'off', // Turn off base rule to use TypeScript version + 'no-unused-vars': 'off', '@typescript-eslint/no-explicit-any': 'warn', - // React-specific rules you wanted 'react-refresh/only-export-components': [ 'warn', { allowConstantExport: true }, @@ -74,9 +73,13 @@ const config = [ unnamedComponents: 'arrow-function', }, ], - 'react/react-in-jsx-scope': 'off', // Not needed with new JSX transform + 'react/react-in-jsx-scope': 'off', + 'react/jsx-boolean-value': ['error', 'never'], + 'react/jsx-curly-brace-presence': [ + 'error', + { props: 'never', children: 'never' }, + ], - // No default React imports - force specific imports 'no-restricted-imports': [ 'error', { @@ -91,41 +94,33 @@ const config = [ }, ], - // Import rules - enforce named exports 'import/no-default-export': 'error', 'import/prefer-default-export': 'off', - // Enforce arrow function shorthand when possible 'arrow-body-style': ['error', 'as-needed'], 'prefer-arrow-callback': ['error', { allowNamedFunctions: false }], - // Forbid console.log usage 'no-console': ['error', { allow: ['warn', 'error'] }], - // TypeScript rules '@typescript-eslint/no-inferrable-types': 'warn', '@typescript-eslint/explicit-function-return-type': 'off', - // SonarJS rules - keep cognitive complexity reasonable 'sonarjs/cognitive-complexity': ['warn', 25], - // Spell checking for code '@cspell/spellchecker': ['warn'], + + 'no-comments/disallowComments': 'error', }, }, { - // TypeScript definition files should have relaxed rules files: ['**/*.d.ts'], rules: { - // Allow unused variables in type definitions 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'off', - // Allow any types in definitions '@typescript-eslint/no-explicit-any': 'off', }, }, { - // Allow default exports for config files files: [ '*.config.*', 'vite.config.*', diff --git a/package-lock.json b/package-lock.json index 418b51a..edc9725 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,7 @@ "@types/node": "^24.2.1", "@types/react": "^19.1.10", "@types/react-dom": "^19.1.7", - "@types/systeminformation": "^3.23.1", + "@types/systeminformation": "^3.54.1", "@typescript-eslint/eslint-plugin": "^8.39.1", "@typescript-eslint/parser": "^8.39.1", "@vitejs/plugin-react": "^5.0.0", @@ -34,6 +34,7 @@ "electron-vite": "^4.0.0", "eslint": "^9.33.0", "eslint-plugin-import": "^2.32.0", + "eslint-plugin-no-comments": "^1.1.10", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", @@ -87,22 +88,22 @@ } }, "node_modules/@babel/core": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", - "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz", + "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.0", + "@babel/generator": "^7.28.3", "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.27.3", - "@babel/helpers": "^7.27.6", - "@babel/parser": "^7.28.0", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.3", + "@babel/parser": "^7.28.3", "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.0", - "@babel/types": "^7.28.0", + "@babel/traverse": "^7.28.3", + "@babel/types": "^7.28.2", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -135,13 +136,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz", - "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.0", - "@babel/types": "^7.28.0", + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -200,15 +201,15 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", - "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.3" + "@babel/traverse": "^7.28.3" }, "engines": { "node": ">=6.9.0" @@ -256,9 +257,9 @@ } }, "node_modules/@babel/helpers": { - "version": "7.28.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.2.tgz", - "integrity": "sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.3.tgz", + "integrity": "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==", "dev": true, "license": "MIT", "dependencies": { @@ -270,12 +271,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", - "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", + "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", "license": "MIT", "dependencies": { - "@babel/types": "^7.28.0" + "@babel/types": "^7.28.2" }, "bin": { "parser": "bin/babel-parser.js" @@ -333,9 +334,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.28.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.2.tgz", - "integrity": "sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz", + "integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -356,17 +357,17 @@ } }, "node_modules/@babel/traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz", - "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz", + "integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.0", + "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.0", + "@babel/parser": "^7.28.3", "@babel/template": "^7.27.2", - "@babel/types": "^7.28.0", + "@babel/types": "^7.28.2", "debug": "^4.3.1" }, "engines": { @@ -3284,11 +3285,15 @@ } }, "node_modules/@types/systeminformation": { - "version": "3.23.1", - "resolved": "https://registry.npmjs.org/@types/systeminformation/-/systeminformation-3.23.1.tgz", - "integrity": "sha512-0/y5m3PQLhQL7UM8MEvwFuLoTT6k5bamcZyjap12S0iHb33ngKfDDqCSnIaCaKWvRE41R/9BsZov1aE6hvcpEg==", + "version": "3.54.1", + "resolved": "https://registry.npmjs.org/@types/systeminformation/-/systeminformation-3.54.1.tgz", + "integrity": "sha512-vvisj2mdWygyc0jk/5XtSVq9gtxCmF3nrGwv8wVway8pwNRhtPji/MU9dc1L0F6rl0F/NFIHa4ScRU7wmNaHmg==", + "deprecated": "This is a stub types definition. systeminformation provides its own type definitions, so you do not need this installed.", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "systeminformation": "*" + } }, "node_modules/@types/verror": { "version": "1.10.11", @@ -5787,9 +5792,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.200", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.200.tgz", - "integrity": "sha512-rFCxROw7aOe4uPTfIAx+rXv9cEcGx+buAF4npnhtTqCJk5KDFRnh3+KYj7rdVh6lsFt5/aPs+Irj9rZ33WMA7w==", + "version": "1.5.201", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.201.tgz", + "integrity": "sha512-ZG65vsrLClodGqywuigc+7m0gr4ISoTQttfVh7nfpLv0M7SIwF4WbFNEOywcqTiujs12AUeeXbFyQieDICAIxg==", "dev": true, "license": "ISC" }, @@ -6386,6 +6391,13 @@ "semver": "bin/semver.js" } }, + "node_modules/eslint-plugin-no-comments": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/eslint-plugin-no-comments/-/eslint-plugin-no-comments-1.1.10.tgz", + "integrity": "sha512-hJohtfKNKDDAmhQ/VsvaN7Q41npFVBeAQISf54ywRQx7EhknF+068SPHj5g3njKMoxYAuk62ahJvJQQvMYeL7g==", + "dev": true, + "license": "MIT" + }, "node_modules/eslint-plugin-react": { "version": "7.37.5", "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", @@ -6829,11 +6841,14 @@ } }, "node_modules/fdir": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", - "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, "peerDependencies": { "picomatch": "^3 || ^4" }, diff --git a/package.json b/package.json index 15fea32..bcd6f42 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "@types/node": "^24.2.1", "@types/react": "^19.1.10", "@types/react-dom": "^19.1.7", - "@types/systeminformation": "^3.23.1", + "@types/systeminformation": "^3.54.1", "@typescript-eslint/eslint-plugin": "^8.39.1", "@typescript-eslint/parser": "^8.39.1", "@vitejs/plugin-react": "^5.0.0", @@ -62,6 +62,7 @@ "electron-vite": "^4.0.0", "eslint": "^9.33.0", "eslint-plugin-import": "^2.32.0", + "eslint-plugin-no-comments": "^1.1.10", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", diff --git a/src/components/UpdateDialog.tsx b/src/components/UpdateDialog.tsx index 6867ddc..4f0be57 100644 --- a/src/components/UpdateDialog.tsx +++ b/src/components/UpdateDialog.tsx @@ -17,7 +17,7 @@ export const UpdateDialog = ({ onAccept, }: UpdateDialogProps) => ( void; onServerOnlyChange: (serverOnly: boolean) => void; + onNoshiftChange: (noshift: boolean) => void; + onFlashattentionChange: (flashattention: boolean) => void; } export const AdvancedTab = ({ additionalArguments, serverOnly, + noshift, + flashattention, onAdditionalArgumentsChange, onServerOnlyChange, + onNoshiftChange, + onFlashattentionChange, }: AdvancedTabProps) => (
@@ -41,5 +49,29 @@ export const AdvancedTab = ({
+ +
+ + onNoshiftChange(!event.currentTarget.checked)} + label="Use ContextShift" + /> + + +
+ +
+ + + onFlashattentionChange(event.currentTarget.checked) + } + label="Use FlashAttention" + /> + + +
); diff --git a/src/components/launch/GeneralTab.tsx b/src/components/launch/GeneralTab.tsx index 32c16a0..38aab1f 100644 --- a/src/components/launch/GeneralTab.tsx +++ b/src/components/launch/GeneralTab.tsx @@ -6,21 +6,32 @@ import { Button, Checkbox, Slider, + Select, + Badge, + Card, } from '@mantine/core'; +import { useState, useEffect } from 'react'; import { File, Search } from 'lucide-react'; import { InfoTooltip } from '@/components/InfoTooltip'; import { getInputValidationState } from '@/utils/validation'; +import { + isOldPcBinary, + isNoCudaBinary, + isRocmBinary, +} from '@/utils/binaryUtils'; interface GeneralTabProps { modelPath: string; gpuLayers: number; autoGpuLayers: boolean; contextSize: number; + backend: string; onModelPathChange: (path: string) => void; onSelectModelFile: () => void; onGpuLayersChange: (layers: number) => void; onAutoGpuLayersChange: (auto: boolean) => void; onContextSizeChange: (size: number) => void; + onBackendChange: (backend: string) => void; } export const GeneralTab = ({ @@ -28,12 +39,98 @@ export const GeneralTab = ({ gpuLayers, autoGpuLayers, contextSize, + backend, onModelPathChange, onSelectModelFile, onGpuLayersChange, onAutoGpuLayersChange, onContextSizeChange, + onBackendChange, }: GeneralTabProps) => { + const [availableBackends, setAvailableBackends] = useState< + Array<{ value: string; label: string; devices?: string[] }> + >([]); + const [isLoadingBackends, setIsLoadingBackends] = useState(true); + + useEffect(() => { + const detectAvailableBackends = async () => { + setIsLoadingBackends(true); + + try { + const [currentVersion, cpuCapabilities, gpuCapabilities] = + await Promise.all([ + window.electronAPI.kobold.getCurrentVersion(), + window.electronAPI.kobold.detectCPU(), + window.electronAPI.kobold.detectGPUCapabilities(), + ]); + + const backends: Array<{ + value: string; + label: string; + devices?: string[]; + }> = []; + + if (currentVersion?.filename) { + const filename = currentVersion.filename; + + if (!isOldPcBinary(filename) && cpuCapabilities.avx2) { + backends.push({ value: 'cpu', label: 'CPU' }); + } + + if (!isNoCudaBinary(filename) && gpuCapabilities.cuda.supported) { + backends.push({ + value: 'cuda', + label: 'CUDA', + devices: gpuCapabilities.cuda.devices, + }); + } + + if (isRocmBinary(filename) && gpuCapabilities.rocm.supported) { + backends.push({ + value: 'rocm', + label: 'ROCm', + devices: gpuCapabilities.rocm.devices, + }); + } + + if (gpuCapabilities.vulkan.supported) { + backends.push({ + value: 'vulkan', + label: 'Vulkan', + devices: gpuCapabilities.vulkan.devices, + }); + } + + if (gpuCapabilities.clblast.supported) { + backends.push({ + value: 'clblast', + label: 'CLBlast', + devices: gpuCapabilities.clblast.devices, + }); + } + } + + backends.push({ value: 'failsafe', label: 'Failsafe' }); + + setAvailableBackends(backends); + + if ( + backends.length > 0 && + (!backend || !backends.some((b) => b.value === backend)) + ) { + onBackendChange(backends[0].value); + } + } catch (error) { + console.warn('Failed to detect available backends:', error); + setAvailableBackends([]); + } finally { + setIsLoadingBackends(false); + } + }; + + void detectAvailableBackends(); + }, [backend, onBackendChange]); + const validationState = getInputValidationState(modelPath); const getInputColor = () => { @@ -97,6 +194,56 @@ export const GeneralTab = ({ +
+ + + Backend + + + +