mirror of
https://github.com/lone-cloud/gerbil
synced 2026-06-03 19:54:44 -07:00
windows rocm support, warn users from suboptimal setups
This commit is contained in:
parent
867ced21f9
commit
01058a8d02
26 changed files with 670 additions and 657 deletions
|
|
@ -2,6 +2,13 @@
|
||||||
|
|
||||||
A koboldcpp manager.
|
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
|
### Prerequisites
|
||||||
|
|
||||||
- Node.js 18+
|
- Node.js 18+
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,8 @@
|
||||||
"flexbox",
|
"flexbox",
|
||||||
"flexdir",
|
"flexdir",
|
||||||
"flexwrap",
|
"flexwrap",
|
||||||
|
"flashattention",
|
||||||
|
"Flashattention",
|
||||||
"fontsize",
|
"fontsize",
|
||||||
"fontweight",
|
"fontweight",
|
||||||
"forwardRef",
|
"forwardRef",
|
||||||
|
|
@ -125,6 +127,8 @@
|
||||||
"nocuda",
|
"nocuda",
|
||||||
"nodeIntegration",
|
"nodeIntegration",
|
||||||
"noheader",
|
"noheader",
|
||||||
|
"noshift",
|
||||||
|
"Noshift",
|
||||||
"nsis",
|
"nsis",
|
||||||
"nvidia",
|
"nvidia",
|
||||||
"oldpc",
|
"oldpc",
|
||||||
|
|
@ -210,6 +214,9 @@
|
||||||
"useReducer",
|
"useReducer",
|
||||||
"useRef",
|
"useRef",
|
||||||
"useState",
|
"useState",
|
||||||
|
"useclblast",
|
||||||
|
"usecuda",
|
||||||
|
"usevulkan",
|
||||||
"util",
|
"util",
|
||||||
"utils",
|
"utils",
|
||||||
"var",
|
"var",
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import react from 'eslint-plugin-react';
|
||||||
import importPlugin from 'eslint-plugin-import';
|
import importPlugin from 'eslint-plugin-import';
|
||||||
import sonarjs from 'eslint-plugin-sonarjs';
|
import sonarjs from 'eslint-plugin-sonarjs';
|
||||||
import cspell from '@cspell/eslint-plugin';
|
import cspell from '@cspell/eslint-plugin';
|
||||||
|
import noComments from 'eslint-plugin-no-comments';
|
||||||
|
|
||||||
const config = [
|
const config = [
|
||||||
js.configs.recommended,
|
js.configs.recommended,
|
||||||
|
|
@ -39,6 +40,7 @@ const config = [
|
||||||
import: importPlugin,
|
import: importPlugin,
|
||||||
sonarjs: sonarjs,
|
sonarjs: sonarjs,
|
||||||
'@cspell': cspell,
|
'@cspell': cspell,
|
||||||
|
'no-comments': noComments,
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
react: {
|
react: {
|
||||||
|
|
@ -46,11 +48,9 @@ const config = [
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
// Use recommended rules from plugins
|
|
||||||
...reactHooks.configs.recommended.rules,
|
...reactHooks.configs.recommended.rules,
|
||||||
...react.configs.recommended.rules,
|
...react.configs.recommended.rules,
|
||||||
|
|
||||||
// Essential TypeScript rules
|
|
||||||
'@typescript-eslint/no-unused-vars': [
|
'@typescript-eslint/no-unused-vars': [
|
||||||
'error',
|
'error',
|
||||||
{
|
{
|
||||||
|
|
@ -59,10 +59,9 @@ const config = [
|
||||||
ignoreRestSiblings: true,
|
ignoreRestSiblings: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
'no-unused-vars': 'off', // Turn off base rule to use TypeScript version
|
'no-unused-vars': 'off',
|
||||||
'@typescript-eslint/no-explicit-any': 'warn',
|
'@typescript-eslint/no-explicit-any': 'warn',
|
||||||
|
|
||||||
// React-specific rules you wanted
|
|
||||||
'react-refresh/only-export-components': [
|
'react-refresh/only-export-components': [
|
||||||
'warn',
|
'warn',
|
||||||
{ allowConstantExport: true },
|
{ allowConstantExport: true },
|
||||||
|
|
@ -74,9 +73,13 @@ const config = [
|
||||||
unnamedComponents: 'arrow-function',
|
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': [
|
'no-restricted-imports': [
|
||||||
'error',
|
'error',
|
||||||
{
|
{
|
||||||
|
|
@ -91,41 +94,33 @@ const config = [
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
// Import rules - enforce named exports
|
|
||||||
'import/no-default-export': 'error',
|
'import/no-default-export': 'error',
|
||||||
'import/prefer-default-export': 'off',
|
'import/prefer-default-export': 'off',
|
||||||
|
|
||||||
// Enforce arrow function shorthand when possible
|
|
||||||
'arrow-body-style': ['error', 'as-needed'],
|
'arrow-body-style': ['error', 'as-needed'],
|
||||||
'prefer-arrow-callback': ['error', { allowNamedFunctions: false }],
|
'prefer-arrow-callback': ['error', { allowNamedFunctions: false }],
|
||||||
|
|
||||||
// Forbid console.log usage
|
|
||||||
'no-console': ['error', { allow: ['warn', 'error'] }],
|
'no-console': ['error', { allow: ['warn', 'error'] }],
|
||||||
|
|
||||||
// TypeScript rules
|
|
||||||
'@typescript-eslint/no-inferrable-types': 'warn',
|
'@typescript-eslint/no-inferrable-types': 'warn',
|
||||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||||
|
|
||||||
// SonarJS rules - keep cognitive complexity reasonable
|
|
||||||
'sonarjs/cognitive-complexity': ['warn', 25],
|
'sonarjs/cognitive-complexity': ['warn', 25],
|
||||||
|
|
||||||
// Spell checking for code
|
|
||||||
'@cspell/spellchecker': ['warn'],
|
'@cspell/spellchecker': ['warn'],
|
||||||
|
|
||||||
|
'no-comments/disallowComments': 'error',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// TypeScript definition files should have relaxed rules
|
|
||||||
files: ['**/*.d.ts'],
|
files: ['**/*.d.ts'],
|
||||||
rules: {
|
rules: {
|
||||||
// Allow unused variables in type definitions
|
|
||||||
'no-unused-vars': 'off',
|
'no-unused-vars': 'off',
|
||||||
'@typescript-eslint/no-unused-vars': 'off',
|
'@typescript-eslint/no-unused-vars': 'off',
|
||||||
// Allow any types in definitions
|
|
||||||
'@typescript-eslint/no-explicit-any': 'off',
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Allow default exports for config files
|
|
||||||
files: [
|
files: [
|
||||||
'*.config.*',
|
'*.config.*',
|
||||||
'vite.config.*',
|
'vite.config.*',
|
||||||
|
|
|
||||||
105
package-lock.json
generated
105
package-lock.json
generated
|
|
@ -23,7 +23,7 @@
|
||||||
"@types/node": "^24.2.1",
|
"@types/node": "^24.2.1",
|
||||||
"@types/react": "^19.1.10",
|
"@types/react": "^19.1.10",
|
||||||
"@types/react-dom": "^19.1.7",
|
"@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/eslint-plugin": "^8.39.1",
|
||||||
"@typescript-eslint/parser": "^8.39.1",
|
"@typescript-eslint/parser": "^8.39.1",
|
||||||
"@vitejs/plugin-react": "^5.0.0",
|
"@vitejs/plugin-react": "^5.0.0",
|
||||||
|
|
@ -34,6 +34,7 @@
|
||||||
"electron-vite": "^4.0.0",
|
"electron-vite": "^4.0.0",
|
||||||
"eslint": "^9.33.0",
|
"eslint": "^9.33.0",
|
||||||
"eslint-plugin-import": "^2.32.0",
|
"eslint-plugin-import": "^2.32.0",
|
||||||
|
"eslint-plugin-no-comments": "^1.1.10",
|
||||||
"eslint-plugin-react": "^7.37.5",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"eslint-plugin-react-hooks": "^5.2.0",
|
"eslint-plugin-react-hooks": "^5.2.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.20",
|
"eslint-plugin-react-refresh": "^0.4.20",
|
||||||
|
|
@ -87,22 +88,22 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/core": {
|
"node_modules/@babel/core": {
|
||||||
"version": "7.28.0",
|
"version": "7.28.3",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz",
|
||||||
"integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==",
|
"integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ampproject/remapping": "^2.2.0",
|
"@ampproject/remapping": "^2.2.0",
|
||||||
"@babel/code-frame": "^7.27.1",
|
"@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-compilation-targets": "^7.27.2",
|
||||||
"@babel/helper-module-transforms": "^7.27.3",
|
"@babel/helper-module-transforms": "^7.28.3",
|
||||||
"@babel/helpers": "^7.27.6",
|
"@babel/helpers": "^7.28.3",
|
||||||
"@babel/parser": "^7.28.0",
|
"@babel/parser": "^7.28.3",
|
||||||
"@babel/template": "^7.27.2",
|
"@babel/template": "^7.27.2",
|
||||||
"@babel/traverse": "^7.28.0",
|
"@babel/traverse": "^7.28.3",
|
||||||
"@babel/types": "^7.28.0",
|
"@babel/types": "^7.28.2",
|
||||||
"convert-source-map": "^2.0.0",
|
"convert-source-map": "^2.0.0",
|
||||||
"debug": "^4.1.0",
|
"debug": "^4.1.0",
|
||||||
"gensync": "^1.0.0-beta.2",
|
"gensync": "^1.0.0-beta.2",
|
||||||
|
|
@ -135,13 +136,13 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/generator": {
|
"node_modules/@babel/generator": {
|
||||||
"version": "7.28.0",
|
"version": "7.28.3",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz",
|
||||||
"integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==",
|
"integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "^7.28.0",
|
"@babel/parser": "^7.28.3",
|
||||||
"@babel/types": "^7.28.0",
|
"@babel/types": "^7.28.2",
|
||||||
"@jridgewell/gen-mapping": "^0.3.12",
|
"@jridgewell/gen-mapping": "^0.3.12",
|
||||||
"@jridgewell/trace-mapping": "^0.3.28",
|
"@jridgewell/trace-mapping": "^0.3.28",
|
||||||
"jsesc": "^3.0.2"
|
"jsesc": "^3.0.2"
|
||||||
|
|
@ -200,15 +201,15 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/helper-module-transforms": {
|
"node_modules/@babel/helper-module-transforms": {
|
||||||
"version": "7.27.3",
|
"version": "7.28.3",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz",
|
||||||
"integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==",
|
"integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-module-imports": "^7.27.1",
|
"@babel/helper-module-imports": "^7.27.1",
|
||||||
"@babel/helper-validator-identifier": "^7.27.1",
|
"@babel/helper-validator-identifier": "^7.27.1",
|
||||||
"@babel/traverse": "^7.27.3"
|
"@babel/traverse": "^7.28.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
|
|
@ -256,9 +257,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/helpers": {
|
"node_modules/@babel/helpers": {
|
||||||
"version": "7.28.2",
|
"version": "7.28.3",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.2.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.3.tgz",
|
||||||
"integrity": "sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==",
|
"integrity": "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -270,12 +271,12 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/parser": {
|
"node_modules/@babel/parser": {
|
||||||
"version": "7.28.0",
|
"version": "7.28.3",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz",
|
||||||
"integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==",
|
"integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/types": "^7.28.0"
|
"@babel/types": "^7.28.2"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"parser": "bin/babel-parser.js"
|
"parser": "bin/babel-parser.js"
|
||||||
|
|
@ -333,9 +334,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/runtime": {
|
"node_modules/@babel/runtime": {
|
||||||
"version": "7.28.2",
|
"version": "7.28.3",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.2.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz",
|
||||||
"integrity": "sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==",
|
"integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
|
|
@ -356,17 +357,17 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/traverse": {
|
"node_modules/@babel/traverse": {
|
||||||
"version": "7.28.0",
|
"version": "7.28.3",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz",
|
||||||
"integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==",
|
"integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.27.1",
|
"@babel/code-frame": "^7.27.1",
|
||||||
"@babel/generator": "^7.28.0",
|
"@babel/generator": "^7.28.3",
|
||||||
"@babel/helper-globals": "^7.28.0",
|
"@babel/helper-globals": "^7.28.0",
|
||||||
"@babel/parser": "^7.28.0",
|
"@babel/parser": "^7.28.3",
|
||||||
"@babel/template": "^7.27.2",
|
"@babel/template": "^7.27.2",
|
||||||
"@babel/types": "^7.28.0",
|
"@babel/types": "^7.28.2",
|
||||||
"debug": "^4.3.1"
|
"debug": "^4.3.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
@ -3284,11 +3285,15 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/systeminformation": {
|
"node_modules/@types/systeminformation": {
|
||||||
"version": "3.23.1",
|
"version": "3.54.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/systeminformation/-/systeminformation-3.23.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/systeminformation/-/systeminformation-3.54.1.tgz",
|
||||||
"integrity": "sha512-0/y5m3PQLhQL7UM8MEvwFuLoTT6k5bamcZyjap12S0iHb33ngKfDDqCSnIaCaKWvRE41R/9BsZov1aE6hvcpEg==",
|
"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,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"systeminformation": "*"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/verror": {
|
"node_modules/@types/verror": {
|
||||||
"version": "1.10.11",
|
"version": "1.10.11",
|
||||||
|
|
@ -5787,9 +5792,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/electron-to-chromium": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.5.200",
|
"version": "1.5.201",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.200.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.201.tgz",
|
||||||
"integrity": "sha512-rFCxROw7aOe4uPTfIAx+rXv9cEcGx+buAF4npnhtTqCJk5KDFRnh3+KYj7rdVh6lsFt5/aPs+Irj9rZ33WMA7w==",
|
"integrity": "sha512-ZG65vsrLClodGqywuigc+7m0gr4ISoTQttfVh7nfpLv0M7SIwF4WbFNEOywcqTiujs12AUeeXbFyQieDICAIxg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
|
@ -6386,6 +6391,13 @@
|
||||||
"semver": "bin/semver.js"
|
"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": {
|
"node_modules/eslint-plugin-react": {
|
||||||
"version": "7.37.5",
|
"version": "7.37.5",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz",
|
||||||
|
|
@ -6829,11 +6841,14 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/fdir": {
|
"node_modules/fdir": {
|
||||||
"version": "6.4.6",
|
"version": "6.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
|
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
||||||
"integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
|
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"picomatch": "^3 || ^4"
|
"picomatch": "^3 || ^4"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@
|
||||||
"@types/node": "^24.2.1",
|
"@types/node": "^24.2.1",
|
||||||
"@types/react": "^19.1.10",
|
"@types/react": "^19.1.10",
|
||||||
"@types/react-dom": "^19.1.7",
|
"@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/eslint-plugin": "^8.39.1",
|
||||||
"@typescript-eslint/parser": "^8.39.1",
|
"@typescript-eslint/parser": "^8.39.1",
|
||||||
"@vitejs/plugin-react": "^5.0.0",
|
"@vitejs/plugin-react": "^5.0.0",
|
||||||
|
|
@ -62,6 +62,7 @@
|
||||||
"electron-vite": "^4.0.0",
|
"electron-vite": "^4.0.0",
|
||||||
"eslint": "^9.33.0",
|
"eslint": "^9.33.0",
|
||||||
"eslint-plugin-import": "^2.32.0",
|
"eslint-plugin-import": "^2.32.0",
|
||||||
|
"eslint-plugin-no-comments": "^1.1.10",
|
||||||
"eslint-plugin-react": "^7.37.5",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"eslint-plugin-react-hooks": "^5.2.0",
|
"eslint-plugin-react-hooks": "^5.2.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.20",
|
"eslint-plugin-react-refresh": "^0.4.20",
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ export const UpdateDialog = ({
|
||||||
onAccept,
|
onAccept,
|
||||||
}: UpdateDialogProps) => (
|
}: UpdateDialogProps) => (
|
||||||
<Modal
|
<Modal
|
||||||
opened={true}
|
opened
|
||||||
onClose={onIgnore}
|
onClose={onIgnore}
|
||||||
title="Update Available"
|
title="Update Available"
|
||||||
centered
|
centered
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,23 @@ import { InfoTooltip } from '@/components/InfoTooltip';
|
||||||
interface AdvancedTabProps {
|
interface AdvancedTabProps {
|
||||||
additionalArguments: string;
|
additionalArguments: string;
|
||||||
serverOnly: boolean;
|
serverOnly: boolean;
|
||||||
|
noshift: boolean;
|
||||||
|
flashattention: boolean;
|
||||||
onAdditionalArgumentsChange: (args: string) => void;
|
onAdditionalArgumentsChange: (args: string) => void;
|
||||||
onServerOnlyChange: (serverOnly: boolean) => void;
|
onServerOnlyChange: (serverOnly: boolean) => void;
|
||||||
|
onNoshiftChange: (noshift: boolean) => void;
|
||||||
|
onFlashattentionChange: (flashattention: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AdvancedTab = ({
|
export const AdvancedTab = ({
|
||||||
additionalArguments,
|
additionalArguments,
|
||||||
serverOnly,
|
serverOnly,
|
||||||
|
noshift,
|
||||||
|
flashattention,
|
||||||
onAdditionalArgumentsChange,
|
onAdditionalArgumentsChange,
|
||||||
onServerOnlyChange,
|
onServerOnlyChange,
|
||||||
|
onNoshiftChange,
|
||||||
|
onFlashattentionChange,
|
||||||
}: AdvancedTabProps) => (
|
}: AdvancedTabProps) => (
|
||||||
<Stack gap="lg">
|
<Stack gap="lg">
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -41,5 +49,29 @@ export const AdvancedTab = ({
|
||||||
<InfoTooltip label="In server-only mode, the KoboldAI Lite web UI won't be displayed. Use this if you'll be using your own frontend." />
|
<InfoTooltip label="In server-only mode, the KoboldAI Lite web UI won't be displayed. Use this if you'll be using your own frontend." />
|
||||||
</Group>
|
</Group>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Group gap="xs" align="center">
|
||||||
|
<Checkbox
|
||||||
|
checked={!noshift}
|
||||||
|
onChange={(event) => onNoshiftChange(!event.currentTarget.checked)}
|
||||||
|
label="Use ContextShift"
|
||||||
|
/>
|
||||||
|
<InfoTooltip label="Use Context Shifting to reduce reprocessing. Recommended" />
|
||||||
|
</Group>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Group gap="xs" align="center">
|
||||||
|
<Checkbox
|
||||||
|
checked={flashattention}
|
||||||
|
onChange={(event) =>
|
||||||
|
onFlashattentionChange(event.currentTarget.checked)
|
||||||
|
}
|
||||||
|
label="Use FlashAttention"
|
||||||
|
/>
|
||||||
|
<InfoTooltip label="Enable flash attention for GGUF models." />
|
||||||
|
</Group>
|
||||||
|
</div>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -6,21 +6,32 @@ import {
|
||||||
Button,
|
Button,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
Slider,
|
Slider,
|
||||||
|
Select,
|
||||||
|
Badge,
|
||||||
|
Card,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
import { File, Search } from 'lucide-react';
|
import { File, Search } from 'lucide-react';
|
||||||
import { InfoTooltip } from '@/components/InfoTooltip';
|
import { InfoTooltip } from '@/components/InfoTooltip';
|
||||||
import { getInputValidationState } from '@/utils/validation';
|
import { getInputValidationState } from '@/utils/validation';
|
||||||
|
import {
|
||||||
|
isOldPcBinary,
|
||||||
|
isNoCudaBinary,
|
||||||
|
isRocmBinary,
|
||||||
|
} from '@/utils/binaryUtils';
|
||||||
|
|
||||||
interface GeneralTabProps {
|
interface GeneralTabProps {
|
||||||
modelPath: string;
|
modelPath: string;
|
||||||
gpuLayers: number;
|
gpuLayers: number;
|
||||||
autoGpuLayers: boolean;
|
autoGpuLayers: boolean;
|
||||||
contextSize: number;
|
contextSize: number;
|
||||||
|
backend: string;
|
||||||
onModelPathChange: (path: string) => void;
|
onModelPathChange: (path: string) => void;
|
||||||
onSelectModelFile: () => void;
|
onSelectModelFile: () => void;
|
||||||
onGpuLayersChange: (layers: number) => void;
|
onGpuLayersChange: (layers: number) => void;
|
||||||
onAutoGpuLayersChange: (auto: boolean) => void;
|
onAutoGpuLayersChange: (auto: boolean) => void;
|
||||||
onContextSizeChange: (size: number) => void;
|
onContextSizeChange: (size: number) => void;
|
||||||
|
onBackendChange: (backend: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GeneralTab = ({
|
export const GeneralTab = ({
|
||||||
|
|
@ -28,12 +39,98 @@ export const GeneralTab = ({
|
||||||
gpuLayers,
|
gpuLayers,
|
||||||
autoGpuLayers,
|
autoGpuLayers,
|
||||||
contextSize,
|
contextSize,
|
||||||
|
backend,
|
||||||
onModelPathChange,
|
onModelPathChange,
|
||||||
onSelectModelFile,
|
onSelectModelFile,
|
||||||
onGpuLayersChange,
|
onGpuLayersChange,
|
||||||
onAutoGpuLayersChange,
|
onAutoGpuLayersChange,
|
||||||
onContextSizeChange,
|
onContextSizeChange,
|
||||||
|
onBackendChange,
|
||||||
}: GeneralTabProps) => {
|
}: 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 validationState = getInputValidationState(modelPath);
|
||||||
|
|
||||||
const getInputColor = () => {
|
const getInputColor = () => {
|
||||||
|
|
@ -97,6 +194,56 @@ export const GeneralTab = ({
|
||||||
</Group>
|
</Group>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Group gap="xs" align="center" mb="xs">
|
||||||
|
<Text size="sm" fw={500}>
|
||||||
|
Backend
|
||||||
|
</Text>
|
||||||
|
<InfoTooltip label="Select a backend to use. CUDA runs on Nvidia GPUs, and is much faster. ROCm is the AMD equivalent. Vulkan and CLBlast works on all GPUs but are somewhat slower." />
|
||||||
|
</Group>
|
||||||
|
<Select
|
||||||
|
placeholder={
|
||||||
|
isLoadingBackends
|
||||||
|
? 'Detecting available backends...'
|
||||||
|
: 'Select backend'
|
||||||
|
}
|
||||||
|
value={backend}
|
||||||
|
onChange={(value) => value && onBackendChange(value)}
|
||||||
|
data={availableBackends.map((b) => ({
|
||||||
|
value: b.value,
|
||||||
|
label: b.label,
|
||||||
|
}))}
|
||||||
|
disabled={isLoadingBackends || availableBackends.length === 0}
|
||||||
|
/>
|
||||||
|
{backend &&
|
||||||
|
availableBackends.find((b) => b.value === backend)?.devices && (
|
||||||
|
<Group gap="xs" mt="xs">
|
||||||
|
<Text size="xs" c="dimmed">
|
||||||
|
Devices:
|
||||||
|
</Text>
|
||||||
|
{availableBackends
|
||||||
|
.find((b) => b.value === backend)
|
||||||
|
?.devices?.map((device, index) => (
|
||||||
|
<Badge key={index} variant="light" size="sm">
|
||||||
|
{device}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</Group>
|
||||||
|
)}
|
||||||
|
{backend === 'cpu' && (
|
||||||
|
<Card withBorder p="sm" mt="xs" bg="blue.0">
|
||||||
|
<Text size="sm" c="blue.8">
|
||||||
|
<Text component="span" fw={600}>
|
||||||
|
Performance Note:
|
||||||
|
</Text>{' '}
|
||||||
|
LLMs run significantly faster on GPU-accelerated systems. Consider
|
||||||
|
using NVIDIA's CUDA or AMD's ROCm backends for optimal
|
||||||
|
performance.
|
||||||
|
</Text>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Group justify="space-between" align="center" mb="xs">
|
<Group justify="space-between" align="center" mb="xs">
|
||||||
<Group gap="xs" align="center">
|
<Group gap="xs" align="center">
|
||||||
|
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
import { Title, Text, Group, Button } from '@mantine/core';
|
|
||||||
|
|
||||||
interface LaunchHeaderProps {
|
|
||||||
selectedFile: string | null;
|
|
||||||
hasUnsavedChanges: boolean;
|
|
||||||
modelPath: string;
|
|
||||||
isLaunching: boolean;
|
|
||||||
onLaunch: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const LaunchHeader = ({
|
|
||||||
selectedFile,
|
|
||||||
hasUnsavedChanges,
|
|
||||||
modelPath,
|
|
||||||
isLaunching,
|
|
||||||
onLaunch,
|
|
||||||
}: LaunchHeaderProps) => (
|
|
||||||
<Group justify="space-between" align="center">
|
|
||||||
<div>
|
|
||||||
<Title order={3}>Launch Configuration</Title>
|
|
||||||
<Text size="sm" c="dimmed">
|
|
||||||
{selectedFile
|
|
||||||
? `Using: ${selectedFile}`
|
|
||||||
: 'No configuration file selected'}
|
|
||||||
{hasUnsavedChanges && (
|
|
||||||
<Text span c="orange">
|
|
||||||
{' '}
|
|
||||||
• Unsaved changes
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
radius="md"
|
|
||||||
disabled={!modelPath || isLaunching}
|
|
||||||
onClick={onLaunch}
|
|
||||||
loading={isLaunching}
|
|
||||||
size="lg"
|
|
||||||
variant="filled"
|
|
||||||
>
|
|
||||||
{isLaunching
|
|
||||||
? 'Launching...'
|
|
||||||
: modelPath
|
|
||||||
? 'Launch KoboldCpp'
|
|
||||||
: 'Select a model file to launch'}
|
|
||||||
</Button>
|
|
||||||
</Group>
|
|
||||||
);
|
|
||||||
|
|
@ -73,11 +73,7 @@ export const NetworkTab = ({
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Text size="sm" fw={500} mb="md">
|
|
||||||
Network Options
|
|
||||||
</Text>
|
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
{/* First row: Multiuser and Multiplayer */}
|
|
||||||
<Group gap="lg" align="flex-start" wrap="nowrap">
|
<Group gap="lg" align="flex-start" wrap="nowrap">
|
||||||
<div style={{ minWidth: '250px' }}>
|
<div style={{ minWidth: '250px' }}>
|
||||||
<Group gap="xs" align="center">
|
<Group gap="xs" align="center">
|
||||||
|
|
@ -106,7 +102,6 @@ export const NetworkTab = ({
|
||||||
</div>
|
</div>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
{/* Second row: Remote Tunnel and No Certify */}
|
|
||||||
<Group gap="lg" align="flex-start" wrap="nowrap">
|
<Group gap="lg" align="flex-start" wrap="nowrap">
|
||||||
<div style={{ minWidth: '250px' }}>
|
<div style={{ minWidth: '250px' }}>
|
||||||
<Group gap="xs" align="center">
|
<Group gap="xs" align="center">
|
||||||
|
|
@ -135,7 +130,6 @@ export const NetworkTab = ({
|
||||||
</div>
|
</div>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
{/* Third row: WebSearch (single item) */}
|
|
||||||
<Group gap="xs" align="center">
|
<Group gap="xs" align="center">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={websearch}
|
checked={websearch}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,14 @@
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { Card, Text, Title, Loader, Stack, Container } from '@mantine/core';
|
import {
|
||||||
|
Card,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
Loader,
|
||||||
|
Stack,
|
||||||
|
Container,
|
||||||
|
Badge,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { DownloadOptionCard } from '@/components/DownloadOptionCard';
|
import { DownloadOptionCard } from '@/components/DownloadOptionCard';
|
||||||
import {
|
import {
|
||||||
getPlatformDisplayName,
|
getPlatformDisplayName,
|
||||||
|
|
@ -27,6 +36,7 @@ export const DownloadScreen = ({ onDownloadComplete }: DownloadScreenProps) => {
|
||||||
const [selectedROCm, setSelectedROCm] = useState<boolean>(false);
|
const [selectedROCm, setSelectedROCm] = useState<boolean>(false);
|
||||||
const [userPlatform, setUserPlatform] = useState<string>('');
|
const [userPlatform, setUserPlatform] = useState<string>('');
|
||||||
const [hasAMDGPU, setHasAMDGPU] = useState<boolean>(false);
|
const [hasAMDGPU, setHasAMDGPU] = useState<boolean>(false);
|
||||||
|
const [hasROCm, setHasROCm] = useState<boolean>(false);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [downloading, setDownloading] = useState(false);
|
const [downloading, setDownloading] = useState(false);
|
||||||
const [downloadProgress, setDownloadProgress] = useState(0);
|
const [downloadProgress, setDownloadProgress] = useState(0);
|
||||||
|
|
@ -57,12 +67,18 @@ export const DownloadScreen = ({ onDownloadComplete }: DownloadScreenProps) => {
|
||||||
try {
|
try {
|
||||||
const gpuInfo = await window.electronAPI.kobold.detectGPU();
|
const gpuInfo = await window.electronAPI.kobold.detectGPU();
|
||||||
setHasAMDGPU(gpuInfo.hasAMD);
|
setHasAMDGPU(gpuInfo.hasAMD);
|
||||||
|
|
||||||
|
if (gpuInfo.hasAMD) {
|
||||||
|
const rocmInfo = await window.electronAPI.kobold.detectROCm();
|
||||||
|
setHasROCm(rocmInfo.supported);
|
||||||
|
}
|
||||||
} catch (gpuError) {
|
} catch (gpuError) {
|
||||||
console.warn(
|
console.warn(
|
||||||
'GPU detection failed, proceeding without GPU info:',
|
'GPU detection failed, proceeding without GPU info:',
|
||||||
gpuError
|
gpuError
|
||||||
);
|
);
|
||||||
setHasAMDGPU(false);
|
setHasAMDGPU(false);
|
||||||
|
setHasROCm(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (releaseData) {
|
if (releaseData) {
|
||||||
|
|
@ -196,6 +212,41 @@ export const DownloadScreen = ({ onDownloadComplete }: DownloadScreenProps) => {
|
||||||
|
|
||||||
{filteredAssets.length > 0 || rocmDownload ? (
|
{filteredAssets.length > 0 || rocmDownload ? (
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
|
{hasAMDGPU && !hasROCm && (
|
||||||
|
<Card withBorder p="md" bg="orange.0">
|
||||||
|
<Stack gap="xs">
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '8px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text fw={600} c="orange.9">
|
||||||
|
AMD GPU Detected
|
||||||
|
</Text>
|
||||||
|
<Tooltip
|
||||||
|
label="ROCm is not installed. Install ROCm for optimal AMD GPU performance with KoboldCpp."
|
||||||
|
multiline
|
||||||
|
w={220}
|
||||||
|
>
|
||||||
|
<Badge
|
||||||
|
size="sm"
|
||||||
|
color="orange"
|
||||||
|
variant="filled"
|
||||||
|
>
|
||||||
|
ROCm Not Found
|
||||||
|
</Badge>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
<Text size="sm" c="orange.8">
|
||||||
|
For best performance with your AMD GPU, consider
|
||||||
|
installing ROCm support.
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
{rocmDownload && hasAMDGPU && renderROCmCard()}
|
{rocmDownload && hasAMDGPU && renderROCmCard()}
|
||||||
|
|
||||||
{sortAssetsByRecommendation(
|
{sortAssetsByRecommendation(
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,9 @@ export const LaunchScreen = ({ onLaunch }: LaunchScreenProps) => {
|
||||||
remotetunnel,
|
remotetunnel,
|
||||||
nocertify,
|
nocertify,
|
||||||
websearch,
|
websearch,
|
||||||
|
noshift,
|
||||||
|
flashattention,
|
||||||
|
backend,
|
||||||
parseAndApplyConfigFile,
|
parseAndApplyConfigFile,
|
||||||
loadSavedSettings,
|
loadSavedSettings,
|
||||||
loadConfigFromFile,
|
loadConfigFromFile,
|
||||||
|
|
@ -61,6 +64,9 @@ export const LaunchScreen = ({ onLaunch }: LaunchScreenProps) => {
|
||||||
handleRemotetunnelChange,
|
handleRemotetunnelChange,
|
||||||
handleNocertifyChange,
|
handleNocertifyChange,
|
||||||
handleWebsearchChange,
|
handleWebsearchChange,
|
||||||
|
handleNoshiftChange,
|
||||||
|
handleFlashattentionChange,
|
||||||
|
handleBackendChange,
|
||||||
} = useLaunchConfig();
|
} = useLaunchConfig();
|
||||||
|
|
||||||
const loadConfigFiles = useCallback(async () => {
|
const loadConfigFiles = useCallback(async () => {
|
||||||
|
|
@ -96,11 +102,9 @@ export const LaunchScreen = ({ onLaunch }: LaunchScreenProps) => {
|
||||||
await parseAndApplyConfigFile(selectedConfig.path);
|
await parseAndApplyConfigFile(selectedConfig.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset unsaved changes when loading a new config
|
|
||||||
setHasUnsavedChanges(false);
|
setHasUnsavedChanges(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Wrapper functions to track changes
|
|
||||||
const handleModelPathChangeWithTracking = (path: string) => {
|
const handleModelPathChangeWithTracking = (path: string) => {
|
||||||
handleModelPathChange(path);
|
handleModelPathChange(path);
|
||||||
setHasUnsavedChanges(true);
|
setHasUnsavedChanges(true);
|
||||||
|
|
@ -141,6 +145,16 @@ export const LaunchScreen = ({ onLaunch }: LaunchScreenProps) => {
|
||||||
setHasUnsavedChanges(true);
|
setHasUnsavedChanges(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleNoshiftChangeWithTracking = (noshift: boolean) => {
|
||||||
|
handleNoshiftChange(noshift);
|
||||||
|
setHasUnsavedChanges(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFlashattentionChangeWithTracking = (flashattention: boolean) => {
|
||||||
|
handleFlashattentionChange(flashattention);
|
||||||
|
setHasUnsavedChanges(true);
|
||||||
|
};
|
||||||
|
|
||||||
const handleMultiuserChangeWithTracking = (multiuser: boolean) => {
|
const handleMultiuserChangeWithTracking = (multiuser: boolean) => {
|
||||||
handleMultiuserChange(multiuser);
|
handleMultiuserChange(multiuser);
|
||||||
setHasUnsavedChanges(true);
|
setHasUnsavedChanges(true);
|
||||||
|
|
@ -166,6 +180,11 @@ export const LaunchScreen = ({ onLaunch }: LaunchScreenProps) => {
|
||||||
setHasUnsavedChanges(true);
|
setHasUnsavedChanges(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleBackendChangeWithTracking = (backend: string) => {
|
||||||
|
handleBackendChange(backend);
|
||||||
|
setHasUnsavedChanges(true);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
void loadConfigFiles();
|
void loadConfigFiles();
|
||||||
|
|
||||||
|
|
@ -180,6 +199,7 @@ export const LaunchScreen = ({ onLaunch }: LaunchScreenProps) => {
|
||||||
return cleanup;
|
return cleanup;
|
||||||
}, [loadConfigFiles]);
|
}, [loadConfigFiles]);
|
||||||
|
|
||||||
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
const handleLaunch = async () => {
|
const handleLaunch = async () => {
|
||||||
if (isLaunching || !modelPath) {
|
if (isLaunching || !modelPath) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -234,6 +254,28 @@ export const LaunchScreen = ({ onLaunch }: LaunchScreenProps) => {
|
||||||
args.push('--websearch');
|
args.push('--websearch');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (noshift) {
|
||||||
|
args.push('--noshift');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flashattention) {
|
||||||
|
args.push('--flashattention');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (backend && backend !== 'cpu') {
|
||||||
|
if (backend === 'cuda') {
|
||||||
|
args.push('--usecuda');
|
||||||
|
} else if (backend === 'rocm') {
|
||||||
|
args.push('--usecuda');
|
||||||
|
} else if (backend === 'vulkan') {
|
||||||
|
args.push('--usevulkan');
|
||||||
|
} else if (backend === 'clblast') {
|
||||||
|
args.push('--useclblast');
|
||||||
|
} else if (backend === 'failsafe') {
|
||||||
|
args.push('--failsafe');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (additionalArguments.trim()) {
|
if (additionalArguments.trim()) {
|
||||||
const additionalArgs = additionalArguments.trim().split(/\s+/);
|
const additionalArgs = additionalArguments.trim().split(/\s+/);
|
||||||
args.push(...additionalArgs);
|
args.push(...additionalArgs);
|
||||||
|
|
@ -285,11 +327,7 @@ export const LaunchScreen = ({ onLaunch }: LaunchScreenProps) => {
|
||||||
size="lg"
|
size="lg"
|
||||||
variant="filled"
|
variant="filled"
|
||||||
>
|
>
|
||||||
{isLaunching
|
{isLaunching ? 'Launching...' : 'Launch'}
|
||||||
? 'Launching...'
|
|
||||||
: modelPath
|
|
||||||
? 'Launch KoboldCpp'
|
|
||||||
: 'Select a model file to launch'}
|
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
@ -301,9 +339,7 @@ export const LaunchScreen = ({ onLaunch }: LaunchScreenProps) => {
|
||||||
onFileSelection={handleFileSelection}
|
onFileSelection={handleFileSelection}
|
||||||
onRefresh={loadConfigFiles}
|
onRefresh={loadConfigFiles}
|
||||||
onSaveAsNew={() => setSaveModalOpened(true)}
|
onSaveAsNew={() => setSaveModalOpened(true)}
|
||||||
onUpdateCurrent={() => {
|
onUpdateCurrent={() => {}}
|
||||||
// TODO: Implement update current configuration
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
|
@ -323,11 +359,13 @@ export const LaunchScreen = ({ onLaunch }: LaunchScreenProps) => {
|
||||||
gpuLayers={gpuLayers}
|
gpuLayers={gpuLayers}
|
||||||
autoGpuLayers={autoGpuLayers}
|
autoGpuLayers={autoGpuLayers}
|
||||||
contextSize={contextSize}
|
contextSize={contextSize}
|
||||||
|
backend={backend}
|
||||||
onModelPathChange={handleModelPathChangeWithTracking}
|
onModelPathChange={handleModelPathChangeWithTracking}
|
||||||
onSelectModelFile={handleSelectModelFile}
|
onSelectModelFile={handleSelectModelFile}
|
||||||
onGpuLayersChange={handleGpuLayersChangeWithTracking}
|
onGpuLayersChange={handleGpuLayersChangeWithTracking}
|
||||||
onAutoGpuLayersChange={handleAutoGpuLayersChangeWithTracking}
|
onAutoGpuLayersChange={handleAutoGpuLayersChangeWithTracking}
|
||||||
onContextSizeChange={handleContextSizeChangeWithTracking}
|
onContextSizeChange={handleContextSizeChangeWithTracking}
|
||||||
|
onBackendChange={handleBackendChangeWithTracking}
|
||||||
/>
|
/>
|
||||||
</Tabs.Panel>
|
</Tabs.Panel>
|
||||||
|
|
||||||
|
|
@ -335,10 +373,16 @@ export const LaunchScreen = ({ onLaunch }: LaunchScreenProps) => {
|
||||||
<AdvancedTab
|
<AdvancedTab
|
||||||
additionalArguments={additionalArguments}
|
additionalArguments={additionalArguments}
|
||||||
serverOnly={serverOnly}
|
serverOnly={serverOnly}
|
||||||
|
noshift={noshift}
|
||||||
|
flashattention={flashattention}
|
||||||
onAdditionalArgumentsChange={
|
onAdditionalArgumentsChange={
|
||||||
handleAdditionalArgumentsChangeWithTracking
|
handleAdditionalArgumentsChangeWithTracking
|
||||||
}
|
}
|
||||||
onServerOnlyChange={handleServerOnlyChangeWithTracking}
|
onServerOnlyChange={handleServerOnlyChangeWithTracking}
|
||||||
|
onNoshiftChange={handleNoshiftChangeWithTracking}
|
||||||
|
onFlashattentionChange={
|
||||||
|
handleFlashattentionChangeWithTracking
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</Tabs.Panel>
|
</Tabs.Panel>
|
||||||
|
|
||||||
|
|
@ -379,7 +423,6 @@ export const LaunchScreen = ({ onLaunch }: LaunchScreenProps) => {
|
||||||
configName={newConfigName}
|
configName={newConfigName}
|
||||||
onConfigNameChange={setNewConfigName}
|
onConfigNameChange={setNewConfigName}
|
||||||
onSave={() => {
|
onSave={() => {
|
||||||
// TODO: Implement save configuration
|
|
||||||
setSaveModalOpened(false);
|
setSaveModalOpened(false);
|
||||||
setNewConfigName('');
|
setNewConfigName('');
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -154,7 +154,7 @@ export const VersionsTab = () => {
|
||||||
installedVersion?.version ||
|
installedVersion?.version ||
|
||||||
latestRelease?.tag_name.replace(/^v/, '') ||
|
latestRelease?.tag_name.replace(/^v/, '') ||
|
||||||
'unknown',
|
'unknown',
|
||||||
size: asset.size,
|
size: installedVersion?.size || asset.size,
|
||||||
isInstalled: Boolean(installedVersion),
|
isInstalled: Boolean(installedVersion),
|
||||||
isCurrent,
|
isCurrent,
|
||||||
downloadUrl: asset.browser_download_url,
|
downloadUrl: asset.browser_download_url,
|
||||||
|
|
@ -175,6 +175,7 @@ export const VersionsTab = () => {
|
||||||
versions.push({
|
versions.push({
|
||||||
name: installed.filename,
|
name: installed.filename,
|
||||||
version: installed.version,
|
version: installed.version,
|
||||||
|
size: installed.size,
|
||||||
isInstalled: true,
|
isInstalled: true,
|
||||||
isCurrent,
|
isCurrent,
|
||||||
installedPath: installed.path,
|
installedPath: installed.path,
|
||||||
|
|
@ -207,21 +208,26 @@ export const VersionsTab = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleVersionSelect = async (version: VersionInfo) => {
|
const makeCurrent = async (version: VersionInfo) => {
|
||||||
if (!version.installedPath) return;
|
if (!version.installedPath) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const filename = version.installedPath.split('/').pop() || '';
|
const success = await window.electronAPI.kobold.setCurrentVersion(
|
||||||
const success =
|
version.installedPath
|
||||||
await window.electronAPI.kobold.setCurrentVersion(filename);
|
);
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
await window.electronAPI.config.set('currentKoboldBinary', filename);
|
|
||||||
await window.electronAPI.config.set(
|
await window.electronAPI.config.set(
|
||||||
'currentKoboldVersion',
|
'currentKoboldBinary',
|
||||||
version.version
|
version.installedPath
|
||||||
);
|
);
|
||||||
await loadInstalledVersions();
|
|
||||||
|
const newCurrentVersion = installedVersions.find(
|
||||||
|
(v) => v.path === version.installedPath
|
||||||
|
);
|
||||||
|
if (newCurrentVersion) {
|
||||||
|
setCurrentVersion(newCurrentVersion);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to set current version:', error);
|
console.error('Failed to set current version:', error);
|
||||||
|
|
@ -299,11 +305,6 @@ export const VersionsTab = () => {
|
||||||
Current
|
Current
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
{version.isInstalled && (
|
|
||||||
<Badge variant="light" color="green" size="sm">
|
|
||||||
Installed
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</Group>
|
</Group>
|
||||||
<Text size="xs" c="dimmed">
|
<Text size="xs" c="dimmed">
|
||||||
Version {version.version}
|
Version {version.version}
|
||||||
|
|
@ -339,10 +340,7 @@ export const VersionsTab = () => {
|
||||||
<Button
|
<Button
|
||||||
variant="light"
|
variant="light"
|
||||||
size="xs"
|
size="xs"
|
||||||
onClick={(e) => {
|
onClick={() => makeCurrent(version)}
|
||||||
e.stopPropagation();
|
|
||||||
handleVersionSelect(version);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
Make Current
|
Make Current
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,16 @@ export const CONFIG_FILE_NAME = 'config.json';
|
||||||
export const GITHUB_API = {
|
export const GITHUB_API = {
|
||||||
BASE_URL: 'https://api.github.com',
|
BASE_URL: 'https://api.github.com',
|
||||||
KOBOLDCPP_REPO: 'LostRuins/koboldcpp',
|
KOBOLDCPP_REPO: 'LostRuins/koboldcpp',
|
||||||
|
KOBOLDCPP_ROCM_REPO: 'YellowRoseCx/koboldcpp-rocm',
|
||||||
get LATEST_RELEASE_URL() {
|
get LATEST_RELEASE_URL() {
|
||||||
return `${this.BASE_URL}/repos/${this.KOBOLDCPP_REPO}/releases/latest`;
|
return `${this.BASE_URL}/repos/${this.KOBOLDCPP_REPO}/releases/latest`;
|
||||||
},
|
},
|
||||||
get ALL_RELEASES_URL() {
|
get ALL_RELEASES_URL() {
|
||||||
return `${this.BASE_URL}/repos/${this.KOBOLDCPP_REPO}/releases`;
|
return `${this.BASE_URL}/repos/${this.KOBOLDCPP_REPO}/releases`;
|
||||||
},
|
},
|
||||||
|
get ROCM_LATEST_RELEASE_URL() {
|
||||||
|
return `${this.BASE_URL}/repos/${this.KOBOLDCPP_ROCM_REPO}/releases/latest`;
|
||||||
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const ASSET_SUFFIXES = {
|
export const ASSET_SUFFIXES = {
|
||||||
|
|
@ -27,6 +31,5 @@ export const KOBOLDAI_URLS = {
|
||||||
export const ROCM = {
|
export const ROCM = {
|
||||||
BINARY_NAME: 'koboldcpp-linux-x64-rocm',
|
BINARY_NAME: 'koboldcpp-linux-x64-rocm',
|
||||||
DOWNLOAD_URL: KOBOLDAI_URLS.ROCM_DOWNLOAD,
|
DOWNLOAD_URL: KOBOLDAI_URLS.ROCM_DOWNLOAD,
|
||||||
ERROR_MESSAGE: 'ROCm version is only available for Linux',
|
SIZE_BYTES: 1024 * 1024 * 1024,
|
||||||
SIZE_BYTES: 1024 * 1024 * 1024, // 1GB
|
|
||||||
} as const;
|
} as const;
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,9 @@ export const useLaunchConfig = () => {
|
||||||
const [remotetunnel, setRemotetunnel] = useState<boolean>(false);
|
const [remotetunnel, setRemotetunnel] = useState<boolean>(false);
|
||||||
const [nocertify, setNocertify] = useState<boolean>(false);
|
const [nocertify, setNocertify] = useState<boolean>(false);
|
||||||
const [websearch, setWebsearch] = useState<boolean>(false);
|
const [websearch, setWebsearch] = useState<boolean>(false);
|
||||||
|
const [noshift, setNoshift] = useState<boolean>(false);
|
||||||
|
const [flashattention, setFlashattention] = useState<boolean>(false);
|
||||||
|
const [backend, setBackend] = useState<string>('');
|
||||||
|
|
||||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
const parseAndApplyConfigFile = useCallback(async (configPath: string) => {
|
const parseAndApplyConfigFile = useCallback(async (configPath: string) => {
|
||||||
|
|
@ -78,6 +81,24 @@ export const useLaunchConfig = () => {
|
||||||
} else {
|
} else {
|
||||||
setWebsearch(false);
|
setWebsearch(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof configData.noshift === 'boolean') {
|
||||||
|
setNoshift(configData.noshift);
|
||||||
|
} else {
|
||||||
|
setNoshift(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof configData.flashattention === 'boolean') {
|
||||||
|
setFlashattention(configData.flashattention);
|
||||||
|
} else {
|
||||||
|
setFlashattention(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof configData.backend === 'string') {
|
||||||
|
setBackend(configData.backend);
|
||||||
|
} else {
|
||||||
|
setBackend('');
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
setGpuLayers(0);
|
setGpuLayers(0);
|
||||||
setContextSize(2048);
|
setContextSize(2048);
|
||||||
|
|
@ -88,6 +109,9 @@ export const useLaunchConfig = () => {
|
||||||
setRemotetunnel(false);
|
setRemotetunnel(false);
|
||||||
setNocertify(false);
|
setNocertify(false);
|
||||||
setWebsearch(false);
|
setWebsearch(false);
|
||||||
|
setNoshift(false);
|
||||||
|
setFlashattention(false);
|
||||||
|
setBackend('');
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
@ -95,7 +119,7 @@ export const useLaunchConfig = () => {
|
||||||
const savedServerOnly = await window.electronAPI.config.getServerOnly();
|
const savedServerOnly = await window.electronAPI.config.getServerOnly();
|
||||||
|
|
||||||
setServerOnly(savedServerOnly);
|
setServerOnly(savedServerOnly);
|
||||||
setModelPath(''); // Model path comes from config file, not saved settings
|
setModelPath('');
|
||||||
setGpuLayers(0);
|
setGpuLayers(0);
|
||||||
setContextSize(2048);
|
setContextSize(2048);
|
||||||
setPort(5001);
|
setPort(5001);
|
||||||
|
|
@ -105,6 +129,9 @@ export const useLaunchConfig = () => {
|
||||||
setRemotetunnel(false);
|
setRemotetunnel(false);
|
||||||
setNocertify(false);
|
setNocertify(false);
|
||||||
setWebsearch(false);
|
setWebsearch(false);
|
||||||
|
setNoshift(false);
|
||||||
|
setFlashattention(false);
|
||||||
|
setBackend('');
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const loadConfigFromFile = useCallback(
|
const loadConfigFromFile = useCallback(
|
||||||
|
|
@ -202,6 +229,18 @@ export const useLaunchConfig = () => {
|
||||||
setWebsearch(checked);
|
setWebsearch(checked);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleNoshiftChange = useCallback((checked: boolean) => {
|
||||||
|
setNoshift(checked);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleFlashattentionChange = useCallback((checked: boolean) => {
|
||||||
|
setFlashattention(checked);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleBackendChange = useCallback((backend: string) => {
|
||||||
|
setBackend(backend);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
serverOnly,
|
serverOnly,
|
||||||
gpuLayers,
|
gpuLayers,
|
||||||
|
|
@ -216,6 +255,9 @@ export const useLaunchConfig = () => {
|
||||||
remotetunnel,
|
remotetunnel,
|
||||||
nocertify,
|
nocertify,
|
||||||
websearch,
|
websearch,
|
||||||
|
noshift,
|
||||||
|
flashattention,
|
||||||
|
backend,
|
||||||
|
|
||||||
parseAndApplyConfigFile,
|
parseAndApplyConfigFile,
|
||||||
loadSavedSettings,
|
loadSavedSettings,
|
||||||
|
|
@ -234,5 +276,8 @@ export const useLaunchConfig = () => {
|
||||||
handleRemotetunnelChange,
|
handleRemotetunnelChange,
|
||||||
handleNocertifyChange,
|
handleNocertifyChange,
|
||||||
handleWebsearchChange,
|
handleWebsearchChange,
|
||||||
|
handleNoshiftChange,
|
||||||
|
handleFlashattentionChange,
|
||||||
|
handleBackendChange,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import { dialog } from 'electron';
|
||||||
import { GitHubService } from '@/main/services/GitHubService';
|
import { GitHubService } from '@/main/services/GitHubService';
|
||||||
import { ConfigManager } from '@/main/managers/ConfigManager';
|
import { ConfigManager } from '@/main/managers/ConfigManager';
|
||||||
import { WindowManager } from '@/main/managers/WindowManager';
|
import { WindowManager } from '@/main/managers/WindowManager';
|
||||||
import { ROCM } from '@/constants';
|
import { ROCM, GITHUB_API } from '@/constants';
|
||||||
|
|
||||||
interface GitHubAsset {
|
interface GitHubAsset {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
@ -49,6 +49,7 @@ export interface InstalledVersion {
|
||||||
version: string;
|
version: string;
|
||||||
path: string;
|
path: string;
|
||||||
filename: string;
|
filename: string;
|
||||||
|
size?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class KoboldCppManager {
|
export class KoboldCppManager {
|
||||||
|
|
@ -161,17 +162,23 @@ export class KoboldCppManager {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!includeVersions) {
|
if (!includeVersions) {
|
||||||
return koboldFiles.map((file) => ({
|
return koboldFiles.map((file) => {
|
||||||
|
const filePath = join(this.installDir, file);
|
||||||
|
const stats = statSync(filePath);
|
||||||
|
return {
|
||||||
version: 'unknown',
|
version: 'unknown',
|
||||||
path: join(this.installDir, file),
|
path: filePath,
|
||||||
filename: file,
|
filename: file,
|
||||||
}));
|
size: stats.size,
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const versionPromises = koboldFiles.map(async (file) => {
|
const versionPromises = koboldFiles.map(async (file) => {
|
||||||
const filePath = join(this.installDir, file);
|
const filePath = join(this.installDir, file);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const stats = statSync(filePath);
|
||||||
const detectedVersion = await this.getVersionFromBinary(filePath);
|
const detectedVersion = await this.getVersionFromBinary(filePath);
|
||||||
const version = detectedVersion || 'unknown';
|
const version = detectedVersion || 'unknown';
|
||||||
|
|
||||||
|
|
@ -179,6 +186,7 @@ export class KoboldCppManager {
|
||||||
version,
|
version,
|
||||||
path: filePath,
|
path: filePath,
|
||||||
filename: file,
|
filename: file,
|
||||||
|
size: stats.size,
|
||||||
} as InstalledVersion;
|
} as InstalledVersion;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(`Could not detect version for ${file}:`, error);
|
console.warn(`Could not detect version for ${file}:`, error);
|
||||||
|
|
@ -195,6 +203,7 @@ export class KoboldCppManager {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getConfigFiles(): Promise<
|
async getConfigFiles(): Promise<
|
||||||
Array<{ name: string; path: string; size: number }>
|
Array<{ name: string; path: string; size: number }>
|
||||||
> {
|
> {
|
||||||
|
|
@ -306,12 +315,9 @@ export class KoboldCppManager {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async setCurrentVersion(version: string): Promise<boolean> {
|
async setCurrentVersion(binaryPath: string): Promise<boolean> {
|
||||||
const versions = await this.getInstalledVersions();
|
if (existsSync(binaryPath)) {
|
||||||
const targetVersion = versions.find((v) => v.version === version);
|
this.configManager.setCurrentKoboldBinary(binaryPath);
|
||||||
|
|
||||||
if (targetVersion && existsSync(targetVersion.path)) {
|
|
||||||
this.configManager.setCurrentKoboldBinary(targetVersion.path);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -376,7 +382,7 @@ export class KoboldCppManager {
|
||||||
try {
|
try {
|
||||||
process.kill('SIGTERM');
|
process.kill('SIGTERM');
|
||||||
} catch {
|
} catch {
|
||||||
// ignore
|
void 0;
|
||||||
}
|
}
|
||||||
resolve(null);
|
resolve(null);
|
||||||
}, 10000);
|
}, 10000);
|
||||||
|
|
@ -479,10 +485,8 @@ export class KoboldCppManager {
|
||||||
version?: string;
|
version?: string;
|
||||||
} | null> {
|
} | null> {
|
||||||
const platform = process.platform;
|
const platform = process.platform;
|
||||||
if (platform !== 'linux') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (platform === 'linux') {
|
||||||
const latestRelease = await this.githubService.getLatestRelease();
|
const latestRelease = await this.githubService.getLatestRelease();
|
||||||
const version = latestRelease?.tag_name?.replace(/^v/, '') || 'unknown';
|
const version = latestRelease?.tag_name?.replace(/^v/, '') || 'unknown';
|
||||||
|
|
||||||
|
|
@ -492,6 +496,34 @@ export class KoboldCppManager {
|
||||||
size: ROCM.SIZE_BYTES,
|
size: ROCM.SIZE_BYTES,
|
||||||
version,
|
version,
|
||||||
};
|
};
|
||||||
|
} else if (platform === 'win32') {
|
||||||
|
try {
|
||||||
|
const response = await fetch(GITHUB_API.ROCM_LATEST_RELEASE_URL);
|
||||||
|
if (!response.ok) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const release = await response.json();
|
||||||
|
const rocmAsset = release.assets?.find(
|
||||||
|
(asset: GitHubAsset) =>
|
||||||
|
asset.name.endsWith('rocm.exe') &&
|
||||||
|
!asset.name.includes('rocm_b2.exe')
|
||||||
|
);
|
||||||
|
|
||||||
|
if (rocmAsset) {
|
||||||
|
return {
|
||||||
|
name: rocmAsset.name,
|
||||||
|
url: rocmAsset.browser_download_url,
|
||||||
|
size: rocmAsset.size,
|
||||||
|
version: release.tag_name?.replace(/^v/, '') || 'unknown',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to fetch Windows ROCm release:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async downloadROCm(onProgress?: (progress: number) => void): Promise<{
|
async downloadROCm(onProgress?: (progress: number) => void): Promise<{
|
||||||
|
|
@ -500,15 +532,15 @@ export class KoboldCppManager {
|
||||||
error?: string;
|
error?: string;
|
||||||
}> {
|
}> {
|
||||||
try {
|
try {
|
||||||
const platform = process.platform;
|
const rocmInfo = await this.getROCmDownload();
|
||||||
if (platform !== 'linux') {
|
if (!rocmInfo) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: ROCM.ERROR_MESSAGE,
|
error: 'ROCm version not available for this platform',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(ROCM.DOWNLOAD_URL);
|
const response = await fetch(rocmInfo.url);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
|
|
@ -516,7 +548,7 @@ export class KoboldCppManager {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const totalBytes = ROCM.SIZE_BYTES;
|
const totalBytes = rocmInfo.size;
|
||||||
let downloadedBytes = 0;
|
let downloadedBytes = 0;
|
||||||
|
|
||||||
const reader = response.body?.getReader();
|
const reader = response.body?.getReader();
|
||||||
|
|
@ -527,7 +559,7 @@ export class KoboldCppManager {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const filePath = join(this.installDir, ROCM.BINARY_NAME);
|
const filePath = join(this.installDir, rocmInfo.name);
|
||||||
const writer = createWriteStream(filePath);
|
const writer = createWriteStream(filePath);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -676,13 +708,11 @@ export class KoboldCppManager {
|
||||||
|
|
||||||
const child = spawn(currentVersion.path, finalArgs, {
|
const child = spawn(currentVersion.path, finalArgs, {
|
||||||
stdio: ['pipe', 'pipe', 'pipe'],
|
stdio: ['pipe', 'pipe', 'pipe'],
|
||||||
detached: true,
|
detached: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.koboldProcess = child;
|
this.koboldProcess = child;
|
||||||
|
|
||||||
child.unref();
|
|
||||||
|
|
||||||
const mainWindow = this.windowManager.getMainWindow();
|
const mainWindow = this.windowManager.getMainWindow();
|
||||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||||
const commandLine = `$ ${currentVersion.path} ${finalArgs.join(' ')}\n${'─'.repeat(60)}\n`;
|
const commandLine = `$ ${currentVersion.path} ${finalArgs.join(' ')}\n${'─'.repeat(60)}\n`;
|
||||||
|
|
@ -737,23 +767,12 @@ export class KoboldCppManager {
|
||||||
stopKoboldCpp(): void {
|
stopKoboldCpp(): void {
|
||||||
if (this.koboldProcess) {
|
if (this.koboldProcess) {
|
||||||
try {
|
try {
|
||||||
// For detached processes, we need to kill the process group
|
|
||||||
if (process.platform !== 'win32') {
|
|
||||||
// On Unix-like systems, kill the process group
|
|
||||||
process.kill(-this.koboldProcess.pid!, 'SIGTERM');
|
|
||||||
} else {
|
|
||||||
// On Windows, just kill the process
|
|
||||||
this.koboldProcess.kill('SIGTERM');
|
this.koboldProcess.kill('SIGTERM');
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (this.koboldProcess && !this.koboldProcess.killed) {
|
if (this.koboldProcess && !this.koboldProcess.killed) {
|
||||||
try {
|
try {
|
||||||
if (process.platform !== 'win32') {
|
|
||||||
process.kill(-this.koboldProcess.pid!, 'SIGKILL');
|
|
||||||
} else {
|
|
||||||
this.koboldProcess.kill('SIGKILL');
|
this.koboldProcess.kill('SIGKILL');
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Error force-killing KoboldCpp process:', error);
|
console.warn('Error force-killing KoboldCpp process:', error);
|
||||||
}
|
}
|
||||||
|
|
@ -785,25 +804,14 @@ export class KoboldCppManager {
|
||||||
this.koboldProcess.once('error', cleanup);
|
this.koboldProcess.once('error', cleanup);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// For detached processes, we need to handle cleanup differently
|
|
||||||
if (process.platform !== 'win32') {
|
|
||||||
// On Unix-like systems, kill the process group
|
|
||||||
process.kill(-this.koboldProcess.pid!, 'SIGTERM');
|
|
||||||
} else {
|
|
||||||
// On Windows, just kill the process
|
|
||||||
this.koboldProcess.kill('SIGTERM');
|
this.koboldProcess.kill('SIGTERM');
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (this.koboldProcess && !this.koboldProcess.killed) {
|
if (this.koboldProcess && !this.koboldProcess.killed) {
|
||||||
try {
|
try {
|
||||||
if (process.platform !== 'win32') {
|
|
||||||
process.kill(-this.koboldProcess.pid!, 'SIGKILL');
|
|
||||||
} else {
|
|
||||||
this.koboldProcess.kill('SIGKILL');
|
this.koboldProcess.kill('SIGKILL');
|
||||||
}
|
|
||||||
} catch {
|
} catch {
|
||||||
// Ignore errors on force kill
|
void 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cleanup();
|
cleanup();
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ import { ConfigManager } from './ConfigManager';
|
||||||
export class WindowManager {
|
export class WindowManager {
|
||||||
private mainWindow: BrowserWindow | null = null;
|
private mainWindow: BrowserWindow | null = null;
|
||||||
private tray: Tray | null = null;
|
private tray: Tray | null = null;
|
||||||
private isQuitting = false;
|
|
||||||
private configManager: ConfigManager;
|
private configManager: ConfigManager;
|
||||||
|
|
||||||
constructor(configManager: ConfigManager) {
|
constructor(configManager: ConfigManager) {
|
||||||
|
|
@ -46,10 +45,13 @@ export class WindowManager {
|
||||||
this.mainWindow = null;
|
this.mainWindow = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Allow navigation to localhost URLs for iframe content
|
const minimizeToTray = this.configManager.get('minimizeToTray') === true;
|
||||||
|
if (minimizeToTray && !this.tray) {
|
||||||
|
this.createSystemTray();
|
||||||
|
}
|
||||||
|
|
||||||
this.mainWindow.webContents.on('will-navigate', (event, navigationUrl) => {
|
this.mainWindow.webContents.on('will-navigate', (event, navigationUrl) => {
|
||||||
const url = new URL(navigationUrl);
|
const url = new URL(navigationUrl);
|
||||||
// Only allow navigation to localhost or the app's origin
|
|
||||||
if (
|
if (
|
||||||
url.hostname !== 'localhost' &&
|
url.hostname !== 'localhost' &&
|
||||||
url.hostname !== '127.0.0.1' &&
|
url.hostname !== '127.0.0.1' &&
|
||||||
|
|
@ -59,46 +61,39 @@ export class WindowManager {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle iframe navigation permissions
|
|
||||||
this.mainWindow.webContents.setWindowOpenHandler(({ url }) => {
|
this.mainWindow.webContents.setWindowOpenHandler(({ url }) => {
|
||||||
const parsedUrl = new URL(url);
|
const parsedUrl = new URL(url);
|
||||||
// Allow localhost URLs to open in the same window/iframe
|
|
||||||
if (
|
if (
|
||||||
parsedUrl.hostname === 'localhost' ||
|
parsedUrl.hostname === 'localhost' ||
|
||||||
parsedUrl.hostname === '127.0.0.1'
|
parsedUrl.hostname === '127.0.0.1'
|
||||||
) {
|
) {
|
||||||
return { action: 'allow' };
|
return { action: 'allow' };
|
||||||
}
|
}
|
||||||
// For other URLs, open in external browser
|
|
||||||
shell.openExternal(url);
|
shell.openExternal(url);
|
||||||
|
|
||||||
return { action: 'deny' };
|
return { action: 'deny' };
|
||||||
});
|
});
|
||||||
|
|
||||||
this.mainWindow.on('close', async (event) => {
|
this.mainWindow.on('close', () => {
|
||||||
if (!this.isQuitting) {
|
app.quit();
|
||||||
const minimizeToTray =
|
|
||||||
this.configManager.get('minimizeToTray') === true;
|
|
||||||
|
|
||||||
if (minimizeToTray) {
|
|
||||||
event.preventDefault();
|
|
||||||
this.mainWindow?.hide();
|
|
||||||
|
|
||||||
if (!this.tray) {
|
|
||||||
this.createSystemTray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.mainWindow.on('minimize', () => {
|
this.mainWindow.on('minimize', () => {
|
||||||
const minimizeToTray = this.configManager.get('minimizeToTray') === true;
|
const minimizeToTray = this.configManager.get('minimizeToTray') === true;
|
||||||
|
|
||||||
if (minimizeToTray) {
|
if (minimizeToTray) {
|
||||||
this.mainWindow?.hide();
|
|
||||||
|
|
||||||
if (!this.tray) {
|
if (!this.tray) {
|
||||||
this.createSystemTray();
|
this.createSystemTray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.tray) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.mainWindow?.setSkipTaskbar(true);
|
||||||
|
this.mainWindow?.hide();
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -111,8 +106,16 @@ export class WindowManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private createSystemTray() {
|
private createSystemTray() {
|
||||||
|
try {
|
||||||
const iconPath = join(app.getAppPath(), 'assets', 'icon.png');
|
const iconPath = join(app.getAppPath(), 'assets', 'icon.png');
|
||||||
this.tray = new Tray(nativeImage.createFromPath(iconPath));
|
|
||||||
|
const image = nativeImage.createFromPath(iconPath);
|
||||||
|
if (!image.isEmpty()) {
|
||||||
|
const resizedImage = image.resize({ width: 16, height: 16 });
|
||||||
|
this.tray = new Tray(resizedImage);
|
||||||
|
} else {
|
||||||
|
this.tray = new Tray(image);
|
||||||
|
}
|
||||||
|
|
||||||
this.tray.setToolTip('Friendly Kobold');
|
this.tray.setToolTip('Friendly Kobold');
|
||||||
|
|
||||||
|
|
@ -120,14 +123,15 @@ export class WindowManager {
|
||||||
{
|
{
|
||||||
label: 'Show',
|
label: 'Show',
|
||||||
click: () => {
|
click: () => {
|
||||||
|
this.mainWindow?.setSkipTaskbar(false);
|
||||||
this.mainWindow?.show();
|
this.mainWindow?.show();
|
||||||
|
this.mainWindow?.focus();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{
|
{
|
||||||
label: 'Quit',
|
label: 'Quit',
|
||||||
click: () => {
|
click: () => {
|
||||||
this.isQuitting = true;
|
|
||||||
app.quit();
|
app.quit();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -136,16 +140,25 @@ export class WindowManager {
|
||||||
this.tray.setContextMenu(trayMenu);
|
this.tray.setContextMenu(trayMenu);
|
||||||
|
|
||||||
this.tray.on('click', () => {
|
this.tray.on('click', () => {
|
||||||
|
this.mainWindow?.setSkipTaskbar(false);
|
||||||
this.mainWindow?.show();
|
this.mainWindow?.show();
|
||||||
|
this.mainWindow?.focus();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.tray.on('double-click', () => {
|
this.tray.on('double-click', () => {
|
||||||
if (this.mainWindow?.isVisible()) {
|
if (this.mainWindow?.isVisible()) {
|
||||||
|
this.mainWindow.setSkipTaskbar(true);
|
||||||
this.mainWindow.hide();
|
this.mainWindow.hide();
|
||||||
} else {
|
} else {
|
||||||
|
this.mainWindow?.setSkipTaskbar(false);
|
||||||
this.mainWindow?.show();
|
this.mainWindow?.show();
|
||||||
|
this.mainWindow?.focus();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to create system tray:', error);
|
||||||
|
this.tray = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public cleanup() {
|
public cleanup() {
|
||||||
|
|
@ -160,6 +173,17 @@ export class WindowManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ensureTrayExists() {
|
||||||
|
const minimizeToTray = this.configManager.get('minimizeToTray') === true;
|
||||||
|
if (minimizeToTray && !this.tray) {
|
||||||
|
this.createSystemTray();
|
||||||
|
} else if (!minimizeToTray && this.tray) {
|
||||||
|
this.tray.removeAllListeners();
|
||||||
|
this.tray.destroy();
|
||||||
|
this.tray = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private setupContextMenu() {
|
private setupContextMenu() {
|
||||||
if (!this.mainWindow) return;
|
if (!this.mainWindow) return;
|
||||||
|
|
||||||
|
|
@ -204,7 +228,6 @@ export class WindowManager {
|
||||||
label: 'Quit',
|
label: 'Quit',
|
||||||
accelerator: process.platform === 'darwin' ? 'Cmd+Q' : 'Ctrl+Q',
|
accelerator: process.platform === 'darwin' ? 'Cmd+Q' : 'Ctrl+Q',
|
||||||
click: () => {
|
click: () => {
|
||||||
this.isQuitting = true;
|
|
||||||
app.quit();
|
app.quit();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -265,18 +288,27 @@ export class WindowManager {
|
||||||
{
|
{
|
||||||
label: 'Help',
|
label: 'Help',
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
|
||||||
label: 'About',
|
|
||||||
click: async () => {
|
|
||||||
await this.showAboutDialog();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: 'KoboldCpp Wiki',
|
label: 'KoboldCpp Wiki',
|
||||||
click: () => {
|
click: () => {
|
||||||
shell.openExternal('https://github.com/LostRuins/koboldcpp/wiki');
|
shell.openExternal('https://github.com/LostRuins/koboldcpp/wiki');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{ type: 'separator' },
|
||||||
|
{
|
||||||
|
label: 'View on GitHub',
|
||||||
|
click: () => {
|
||||||
|
shell.openExternal(
|
||||||
|
'https://github.com/lone-cloud/friendly-kobold'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'About',
|
||||||
|
click: async () => {
|
||||||
|
await this.showAboutDialog();
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
import si from 'systeminformation';
|
|
||||||
|
|
||||||
export interface CPUCapabilities {
|
|
||||||
avx: boolean;
|
|
||||||
avx2: boolean;
|
|
||||||
cpuInfo: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CPUService {
|
|
||||||
async detectCPUCapabilities(): Promise<CPUCapabilities> {
|
|
||||||
try {
|
|
||||||
const [cpu, flags] = await Promise.all([si.cpu(), si.cpuFlags()]);
|
|
||||||
|
|
||||||
const cpuInfo: string[] = [];
|
|
||||||
if (cpu.brand) {
|
|
||||||
cpuInfo.push(cpu.brand);
|
|
||||||
}
|
|
||||||
if (cpu.cores) {
|
|
||||||
cpuInfo.push(`${cpu.cores} cores`);
|
|
||||||
}
|
|
||||||
if (cpu.speed) {
|
|
||||||
cpuInfo.push(`${cpu.speed}GHz`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const avx = flags.includes('avx') || flags.includes('AVX');
|
|
||||||
const avx2 = flags.includes('avx2') || flags.includes('AVX2');
|
|
||||||
|
|
||||||
return {
|
|
||||||
avx,
|
|
||||||
avx2,
|
|
||||||
cpuInfo: cpuInfo.length > 0 ? cpuInfo : ['CPU information unavailable'],
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('CPU detection failed:', error);
|
|
||||||
return {
|
|
||||||
avx: false,
|
|
||||||
avx2: false,
|
|
||||||
cpuInfo: ['CPU detection failed'],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,335 +0,0 @@
|
||||||
import si from 'systeminformation';
|
|
||||||
|
|
||||||
export interface GPUCapabilities {
|
|
||||||
cuda: {
|
|
||||||
supported: boolean;
|
|
||||||
devices: string[];
|
|
||||||
};
|
|
||||||
rocm: {
|
|
||||||
supported: boolean;
|
|
||||||
devices: string[];
|
|
||||||
};
|
|
||||||
vulkan: {
|
|
||||||
supported: boolean;
|
|
||||||
devices: string[];
|
|
||||||
};
|
|
||||||
clblast: {
|
|
||||||
supported: boolean;
|
|
||||||
devices: string[];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export class GPUService {
|
|
||||||
async detectGPU(): Promise<{
|
|
||||||
hasAMD: boolean;
|
|
||||||
hasNVIDIA: boolean;
|
|
||||||
gpuInfo: string[];
|
|
||||||
}> {
|
|
||||||
try {
|
|
||||||
const graphics = await si.graphics();
|
|
||||||
|
|
||||||
let hasAMD = false;
|
|
||||||
let hasNVIDIA = false;
|
|
||||||
const gpuInfo: string[] = [];
|
|
||||||
|
|
||||||
for (const controller of graphics.controllers) {
|
|
||||||
if (controller.model) {
|
|
||||||
gpuInfo.push(controller.model);
|
|
||||||
}
|
|
||||||
|
|
||||||
const vendor = controller.vendor?.toLowerCase() || '';
|
|
||||||
const model = controller.model?.toLowerCase() || '';
|
|
||||||
|
|
||||||
if (
|
|
||||||
vendor.includes('amd') ||
|
|
||||||
vendor.includes('ati') ||
|
|
||||||
model.includes('radeon') ||
|
|
||||||
model.includes('amd')
|
|
||||||
) {
|
|
||||||
hasAMD = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
vendor.includes('nvidia') ||
|
|
||||||
model.includes('nvidia') ||
|
|
||||||
model.includes('geforce') ||
|
|
||||||
model.includes('gtx') ||
|
|
||||||
model.includes('rtx')
|
|
||||||
) {
|
|
||||||
hasNVIDIA = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
hasAMD,
|
|
||||||
hasNVIDIA,
|
|
||||||
gpuInfo:
|
|
||||||
gpuInfo.length > 0 ? gpuInfo : ['No GPU information available'],
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('GPU detection failed:', error);
|
|
||||||
return {
|
|
||||||
hasAMD: false,
|
|
||||||
hasNVIDIA: false,
|
|
||||||
gpuInfo: ['GPU detection failed'],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async detectGPUCapabilities(): Promise<GPUCapabilities> {
|
|
||||||
const [cuda, rocm, vulkan, clblast] = await Promise.all([
|
|
||||||
this.detectCUDA(),
|
|
||||||
this.detectROCm(),
|
|
||||||
this.detectVulkan(),
|
|
||||||
this.detectCLBlast(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return { cuda, rocm, vulkan, clblast };
|
|
||||||
}
|
|
||||||
|
|
||||||
private async detectCUDA(): Promise<{
|
|
||||||
supported: boolean;
|
|
||||||
devices: string[];
|
|
||||||
}> {
|
|
||||||
try {
|
|
||||||
const { spawn } = await import('child_process');
|
|
||||||
const nvidia = spawn(
|
|
||||||
'nvidia-smi',
|
|
||||||
['--query-gpu=name,memory.total,memory.free', '--format=csv,noheader'],
|
|
||||||
{ timeout: 5000 }
|
|
||||||
);
|
|
||||||
|
|
||||||
let output = '';
|
|
||||||
nvidia.stdout.on('data', (data) => {
|
|
||||||
output += data.toString();
|
|
||||||
});
|
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
nvidia.on('close', (code) => {
|
|
||||||
if (code === 0 && output.trim()) {
|
|
||||||
const devices = output
|
|
||||||
.trim()
|
|
||||||
.split('\n')
|
|
||||||
.map((line) => {
|
|
||||||
const parts = line.split(',');
|
|
||||||
return parts[0]?.trim() || 'Unknown NVIDIA GPU';
|
|
||||||
})
|
|
||||||
.filter(Boolean);
|
|
||||||
|
|
||||||
resolve({
|
|
||||||
supported: devices.length > 0,
|
|
||||||
devices,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
resolve({ supported: false, devices: [] });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
nvidia.on('error', () => {
|
|
||||||
resolve({ supported: false, devices: [] });
|
|
||||||
});
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
try {
|
|
||||||
nvidia.kill('SIGTERM');
|
|
||||||
} catch {
|
|
||||||
// Process already terminated
|
|
||||||
}
|
|
||||||
resolve({ supported: false, devices: [] });
|
|
||||||
}, 5000);
|
|
||||||
});
|
|
||||||
} catch {
|
|
||||||
return { supported: false, devices: [] };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async detectROCm(): Promise<{
|
|
||||||
supported: boolean;
|
|
||||||
devices: string[];
|
|
||||||
}> {
|
|
||||||
try {
|
|
||||||
const { spawn } = await import('child_process');
|
|
||||||
const rocminfo = spawn('rocminfo', [], { timeout: 5000 });
|
|
||||||
|
|
||||||
let output = '';
|
|
||||||
rocminfo.stdout.on('data', (data) => {
|
|
||||||
output += data.toString();
|
|
||||||
});
|
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
rocminfo.on('close', (code) => {
|
|
||||||
if (code === 0 && output.trim()) {
|
|
||||||
const devices: string[] = [];
|
|
||||||
const lines = output.split('\n');
|
|
||||||
|
|
||||||
for (let i = 0; i < lines.length; i++) {
|
|
||||||
const line = lines[i];
|
|
||||||
if (line.includes('Marketing Name:')) {
|
|
||||||
const name = line.split('Marketing Name:')[1]?.trim();
|
|
||||||
if (name && !name.includes('CPU')) {
|
|
||||||
devices.push(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve({
|
|
||||||
supported: devices.length > 0,
|
|
||||||
devices,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
resolve({ supported: false, devices: [] });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
rocminfo.on('error', () => {
|
|
||||||
resolve({ supported: false, devices: [] });
|
|
||||||
});
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
try {
|
|
||||||
rocminfo.kill('SIGTERM');
|
|
||||||
} catch {
|
|
||||||
// Process already terminated
|
|
||||||
}
|
|
||||||
resolve({ supported: false, devices: [] });
|
|
||||||
}, 5000);
|
|
||||||
});
|
|
||||||
} catch {
|
|
||||||
return { supported: false, devices: [] };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async detectVulkan(): Promise<{
|
|
||||||
supported: boolean;
|
|
||||||
devices: string[];
|
|
||||||
}> {
|
|
||||||
try {
|
|
||||||
const { spawn } = await import('child_process');
|
|
||||||
const vulkaninfo = spawn('vulkaninfo', ['--summary'], { timeout: 5000 });
|
|
||||||
|
|
||||||
let output = '';
|
|
||||||
vulkaninfo.stdout.on('data', (data) => {
|
|
||||||
output += data.toString();
|
|
||||||
});
|
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
vulkaninfo.on('close', (code) => {
|
|
||||||
if (code === 0 && output.trim()) {
|
|
||||||
const devices: string[] = [];
|
|
||||||
const lines = output.split('\n');
|
|
||||||
|
|
||||||
for (const line of lines) {
|
|
||||||
if (line.includes('deviceName')) {
|
|
||||||
const name = line.split('=')[1]?.trim();
|
|
||||||
if (name) {
|
|
||||||
devices.push(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve({
|
|
||||||
supported: devices.length > 0,
|
|
||||||
devices,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
resolve({ supported: false, devices: [] });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
vulkaninfo.on('error', () => {
|
|
||||||
resolve({ supported: false, devices: [] });
|
|
||||||
});
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
try {
|
|
||||||
vulkaninfo.kill('SIGTERM');
|
|
||||||
} catch {
|
|
||||||
// Process already terminated
|
|
||||||
}
|
|
||||||
resolve({ supported: false, devices: [] });
|
|
||||||
}, 5000);
|
|
||||||
});
|
|
||||||
} catch {
|
|
||||||
return { supported: false, devices: [] };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async detectCLBlast(): Promise<{
|
|
||||||
supported: boolean;
|
|
||||||
devices: string[];
|
|
||||||
}> {
|
|
||||||
try {
|
|
||||||
const { spawn } = await import('child_process');
|
|
||||||
const clinfo = spawn('clinfo', ['--json'], { timeout: 5000 });
|
|
||||||
|
|
||||||
let output = '';
|
|
||||||
clinfo.stdout.on('data', (data) => {
|
|
||||||
output += data.toString();
|
|
||||||
});
|
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
clinfo.on('close', (code) => {
|
|
||||||
if (code === 0 && output.trim()) {
|
|
||||||
try {
|
|
||||||
const data = JSON.parse(output);
|
|
||||||
const devices: string[] = [];
|
|
||||||
|
|
||||||
if (data.platforms) {
|
|
||||||
for (const platform of data.platforms) {
|
|
||||||
if (platform.devices) {
|
|
||||||
for (const device of platform.devices) {
|
|
||||||
if (device.name && device.type !== 'CPU') {
|
|
||||||
devices.push(device.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve({
|
|
||||||
supported: devices.length > 0,
|
|
||||||
devices,
|
|
||||||
});
|
|
||||||
} catch {
|
|
||||||
// Failed to parse JSON, but clinfo ran successfully
|
|
||||||
// Try to extract device names from text output
|
|
||||||
const lines = output.split('\n');
|
|
||||||
const devices: string[] = [];
|
|
||||||
|
|
||||||
for (const line of lines) {
|
|
||||||
if (line.includes('Device Name') && !line.includes('CPU')) {
|
|
||||||
const name = line.split(':')[1]?.trim();
|
|
||||||
if (name) {
|
|
||||||
devices.push(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve({
|
|
||||||
supported: devices.length > 0,
|
|
||||||
devices,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
resolve({ supported: false, devices: [] });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
clinfo.on('error', () => {
|
|
||||||
resolve({ supported: false, devices: [] });
|
|
||||||
});
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
try {
|
|
||||||
clinfo.kill('SIGTERM');
|
|
||||||
} catch {
|
|
||||||
// Process already terminated
|
|
||||||
}
|
|
||||||
resolve({ supported: false, devices: [] });
|
|
||||||
}, 5000);
|
|
||||||
});
|
|
||||||
} catch {
|
|
||||||
return { supported: false, devices: [] };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { GITHUB_API } from '@/constants';
|
||||||
|
|
||||||
export class GitHubService {
|
export class GitHubService {
|
||||||
private lastApiCall = 0;
|
private lastApiCall = 0;
|
||||||
private apiCooldown = 60000; // 1 minute cooldown
|
private apiCooldown = 60000;
|
||||||
private cachedRelease: GitHubRelease | null = null;
|
private cachedRelease: GitHubRelease | null = null;
|
||||||
private cachedReleases: GitHubRelease[] = [];
|
private cachedReleases: GitHubRelease[] = [];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -165,7 +165,7 @@ export class HardwareService {
|
||||||
try {
|
try {
|
||||||
nvidia.kill('SIGTERM');
|
nvidia.kill('SIGTERM');
|
||||||
} catch {
|
} catch {
|
||||||
// Process already terminated
|
void 0;
|
||||||
}
|
}
|
||||||
resolve({ supported: false, devices: [] });
|
resolve({ supported: false, devices: [] });
|
||||||
}, 5000);
|
}, 5000);
|
||||||
|
|
@ -175,7 +175,7 @@ export class HardwareService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async detectROCm(): Promise<{
|
async detectROCm(): Promise<{
|
||||||
supported: boolean;
|
supported: boolean;
|
||||||
devices: string[];
|
devices: string[];
|
||||||
}> {
|
}> {
|
||||||
|
|
@ -189,6 +189,7 @@ export class HardwareService {
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
rocminfo.on('close', (code) => {
|
rocminfo.on('close', (code) => {
|
||||||
if (code === 0 && output.trim()) {
|
if (code === 0 && output.trim()) {
|
||||||
const devices: string[] = [];
|
const devices: string[] = [];
|
||||||
|
|
@ -196,13 +197,30 @@ export class HardwareService {
|
||||||
|
|
||||||
for (let i = 0; i < lines.length; i++) {
|
for (let i = 0; i < lines.length; i++) {
|
||||||
const line = lines[i];
|
const line = lines[i];
|
||||||
|
|
||||||
if (line.includes('Marketing Name:')) {
|
if (line.includes('Marketing Name:')) {
|
||||||
const name = line.split('Marketing Name:')[1]?.trim();
|
const name = line.split('Marketing Name:')[1]?.trim();
|
||||||
if (name && !name.includes('CPU')) {
|
if (name) {
|
||||||
|
let deviceType = '';
|
||||||
|
|
||||||
|
for (
|
||||||
|
let j = Math.max(0, i - 10);
|
||||||
|
j < Math.min(lines.length, i + 10);
|
||||||
|
j++
|
||||||
|
) {
|
||||||
|
if (lines[j].includes('Device Type:')) {
|
||||||
|
deviceType =
|
||||||
|
lines[j].split('Device Type:')[1]?.trim() || '';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deviceType !== 'CPU') {
|
||||||
devices.push(name);
|
devices.push(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resolve({
|
resolve({
|
||||||
supported: devices.length > 0,
|
supported: devices.length > 0,
|
||||||
|
|
@ -221,7 +239,7 @@ export class HardwareService {
|
||||||
try {
|
try {
|
||||||
rocminfo.kill('SIGTERM');
|
rocminfo.kill('SIGTERM');
|
||||||
} catch {
|
} catch {
|
||||||
// Process already terminated
|
void 0;
|
||||||
}
|
}
|
||||||
resolve({ supported: false, devices: [] });
|
resolve({ supported: false, devices: [] });
|
||||||
}, 5000);
|
}, 5000);
|
||||||
|
|
@ -276,7 +294,7 @@ export class HardwareService {
|
||||||
try {
|
try {
|
||||||
vulkaninfo.kill('SIGTERM');
|
vulkaninfo.kill('SIGTERM');
|
||||||
} catch {
|
} catch {
|
||||||
// Process already terminated
|
void 0;
|
||||||
}
|
}
|
||||||
resolve({ supported: false, devices: [] });
|
resolve({ supported: false, devices: [] });
|
||||||
}, 5000);
|
}, 5000);
|
||||||
|
|
@ -324,7 +342,6 @@ export class HardwareService {
|
||||||
devices,
|
devices,
|
||||||
});
|
});
|
||||||
} catch {
|
} catch {
|
||||||
// Failed to parse JSON, try text parsing
|
|
||||||
const lines = output.split('\n');
|
const lines = output.split('\n');
|
||||||
const devices: string[] = [];
|
const devices: string[] = [];
|
||||||
|
|
||||||
|
|
@ -355,7 +372,7 @@ export class HardwareService {
|
||||||
try {
|
try {
|
||||||
clinfo.kill('SIGTERM');
|
clinfo.kill('SIGTERM');
|
||||||
} catch {
|
} catch {
|
||||||
// Process already terminated
|
void 0;
|
||||||
}
|
}
|
||||||
resolve({ supported: false, devices: [] });
|
resolve({ supported: false, devices: [] });
|
||||||
}, 5000);
|
}, 5000);
|
||||||
|
|
|
||||||
|
|
@ -112,6 +112,10 @@ export class IPCHandlers {
|
||||||
this.hardwareService.detectGPUCapabilities()
|
this.hardwareService.detectGPUCapabilities()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ipcMain.handle('kobold:detectROCm', () =>
|
||||||
|
this.hardwareService.detectROCm()
|
||||||
|
);
|
||||||
|
|
||||||
ipcMain.handle('kobold:detectHardware', () =>
|
ipcMain.handle('kobold:detectHardware', () =>
|
||||||
this.hardwareService.detectAll()
|
this.hardwareService.detectAll()
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ const koboldAPI: KoboldAPI = {
|
||||||
detectCPU: () => ipcRenderer.invoke('kobold:detectCPU'),
|
detectCPU: () => ipcRenderer.invoke('kobold:detectCPU'),
|
||||||
detectGPUCapabilities: () =>
|
detectGPUCapabilities: () =>
|
||||||
ipcRenderer.invoke('kobold:detectGPUCapabilities'),
|
ipcRenderer.invoke('kobold:detectGPUCapabilities'),
|
||||||
|
detectROCm: () => ipcRenderer.invoke('kobold:detectROCm'),
|
||||||
detectHardware: () => ipcRenderer.invoke('kobold:detectHardware'),
|
detectHardware: () => ipcRenderer.invoke('kobold:detectHardware'),
|
||||||
detectAllCapabilities: () =>
|
detectAllCapabilities: () =>
|
||||||
ipcRenderer.invoke('kobold:detectAllCapabilities'),
|
ipcRenderer.invoke('kobold:detectAllCapabilities'),
|
||||||
|
|
|
||||||
2
src/types/electron.d.ts
vendored
2
src/types/electron.d.ts
vendored
|
|
@ -43,6 +43,7 @@ interface InstalledVersion {
|
||||||
path: string;
|
path: string;
|
||||||
type: 'github' | 'rocm';
|
type: 'github' | 'rocm';
|
||||||
filename: string;
|
filename: string;
|
||||||
|
size?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ROCmDownload {
|
interface ROCmDownload {
|
||||||
|
|
@ -66,6 +67,7 @@ export interface KoboldAPI {
|
||||||
detectGPU: () => Promise<BasicGPUInfo>;
|
detectGPU: () => Promise<BasicGPUInfo>;
|
||||||
detectCPU: () => Promise<CPUCapabilities>;
|
detectCPU: () => Promise<CPUCapabilities>;
|
||||||
detectGPUCapabilities: () => Promise<GPUCapabilities>;
|
detectGPUCapabilities: () => Promise<GPUCapabilities>;
|
||||||
|
detectROCm: () => Promise<{ supported: boolean; devices: string[] }>;
|
||||||
detectHardware: () => Promise<HardwareInfo>;
|
detectHardware: () => Promise<HardwareInfo>;
|
||||||
detectAllCapabilities: () => Promise<HardwareInfo>;
|
detectAllCapabilities: () => Promise<HardwareInfo>;
|
||||||
getCurrentInstallDir: () => Promise<string>;
|
getCurrentInstallDir: () => Promise<string>;
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ export interface InstalledVersion {
|
||||||
version: string;
|
version: string;
|
||||||
path: string;
|
path: string;
|
||||||
filename: string;
|
filename: string;
|
||||||
|
size?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ROCmDownload {
|
export interface ROCmDownload {
|
||||||
|
|
|
||||||
35
src/utils/binaryUtils.ts
Normal file
35
src/utils/binaryUtils.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
export const removeBinaryExtension = (filename: string): string => {
|
||||||
|
if (filename.endsWith('.exe')) {
|
||||||
|
return filename.slice(0, -4);
|
||||||
|
}
|
||||||
|
return filename;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getBinaryBaseName = (filename: string): string => {
|
||||||
|
const baseName = filename.split(/[/\\]/).pop() || filename;
|
||||||
|
return removeBinaryExtension(baseName).toLowerCase();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isOldPcBinary = (filename: string): boolean => {
|
||||||
|
const baseName = getBinaryBaseName(filename);
|
||||||
|
return baseName.endsWith('oldpc');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isNoCudaBinary = (filename: string): boolean => {
|
||||||
|
const baseName = getBinaryBaseName(filename);
|
||||||
|
return baseName.endsWith('nocuda');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isRocmBinary = (filename: string): boolean => {
|
||||||
|
const baseName = getBinaryBaseName(filename);
|
||||||
|
return baseName.endsWith('rocm');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getBinaryType = (
|
||||||
|
filename: string
|
||||||
|
): 'oldpc' | 'nocuda' | 'rocm' | 'standard' => {
|
||||||
|
if (isOldPcBinary(filename)) return 'oldpc';
|
||||||
|
if (isNoCudaBinary(filename)) return 'nocuda';
|
||||||
|
if (isRocmBinary(filename)) return 'rocm';
|
||||||
|
return 'standard';
|
||||||
|
};
|
||||||
Loading…
Add table
Reference in a new issue