mirror of
https://github.com/lone-cloud/gerbil
synced 2026-06-03 19:54:44 -07:00
much better backend support logic and configuration, new advanced tab configs, LoRa support for image gen
This commit is contained in:
parent
13f864b3b9
commit
617f13f4ca
29 changed files with 1004 additions and 546 deletions
2
.github/RELEASE.md
vendored
2
.github/RELEASE.md
vendored
|
|
@ -54,7 +54,7 @@ If the build fails:
|
||||||
|
|
||||||
To customize the app icon:
|
To customize the app icon:
|
||||||
|
|
||||||
1. Replace `assets/icon_512.png` with your custom 512x512 PNG icon
|
1. Replace `assets/icon.png` with your custom 512x512 PNG icon
|
||||||
2. Electron Builder automatically converts this single PNG to the appropriate format for each platform:
|
2. Electron Builder automatically converts this single PNG to the appropriate format for each platform:
|
||||||
- macOS: Converts to `.icns` format
|
- macOS: Converts to `.icns` format
|
||||||
- Windows: Converts to `.ico` format
|
- Windows: Converts to `.ico` format
|
||||||
|
|
|
||||||
12
.github/workflows/ci.yml
vendored
12
.github/workflows/ci.yml
vendored
|
|
@ -26,14 +26,8 @@ jobs:
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: yarn
|
run: yarn
|
||||||
|
|
||||||
- name: Run type check
|
- name: Run checks (lint, type check, spell check)
|
||||||
run: yarn compile
|
run: yarn check-all
|
||||||
|
|
||||||
- name: Run linter
|
|
||||||
run: yarn lint
|
|
||||||
|
|
||||||
- name: Run spell check
|
|
||||||
run: yarn spell-check
|
|
||||||
|
|
||||||
- name: Test build
|
- name: Test build
|
||||||
run: yarn build:electron
|
run: yarn build
|
||||||
|
|
|
||||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
|
|
@ -42,7 +42,7 @@ jobs:
|
||||||
run: yarn lint
|
run: yarn lint
|
||||||
|
|
||||||
- name: Build Electron app
|
- name: Build Electron app
|
||||||
run: yarn build
|
run: yarn package
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
|
|
||||||
2
.npmrc
2
.npmrc
|
|
@ -1,2 +0,0 @@
|
||||||
# Yarn configuration for Electron compatibility
|
|
||||||
nodeLinker: node-modules
|
|
||||||
34
.vscode/spell-checker.md
vendored
34
.vscode/spell-checker.md
vendored
|
|
@ -1,34 +0,0 @@
|
||||||
# VS Code Spell Checker Settings
|
|
||||||
|
|
||||||
Add this to your VS Code workspace settings (`.vscode/settings.json`) for the best spell checking experience:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"cSpell.enabled": true,
|
|
||||||
"cSpell.showCommandsInEditorContextMenu": true,
|
|
||||||
"cSpell.showStatus": true,
|
|
||||||
"cSpell.diagnosticLevel": "Warning",
|
|
||||||
"cSpell.checkLimit": 500,
|
|
||||||
"cSpell.numSuggestions": 8,
|
|
||||||
"cSpell.suggestionMenuType": "quickPick",
|
|
||||||
"cSpell.allowCompoundWords": true,
|
|
||||||
"cSpell.enableFiletypes": [
|
|
||||||
"javascript",
|
|
||||||
"typescript",
|
|
||||||
"javascriptreact",
|
|
||||||
"typescriptreact",
|
|
||||||
"markdown",
|
|
||||||
"json",
|
|
||||||
"css",
|
|
||||||
"html"
|
|
||||||
],
|
|
||||||
"cSpell.ignorePaths": [
|
|
||||||
"node_modules/**",
|
|
||||||
"dist/**",
|
|
||||||
"dist-electron/**",
|
|
||||||
"**/*.min.js",
|
|
||||||
"**/*.min.css",
|
|
||||||
"package-lock.json"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
@ -9,6 +9,7 @@ A koboldcpp manager.
|
||||||
- better surface the ROCm-specific builds of koboldcpp from YellowRoseCx and from [koboldai.org](https://koboldai.org/cpplinuxrocm)
|
- 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
|
- manage the koboldcpp binary to prevent it from running in the background indefinitely
|
||||||
- automatically unpack all downloaded koboldcpp binaries for significantly faster operation and reduced RAM+HDD utilization (up to ~4GB less RAM usage for ROCm)
|
- automatically unpack all downloaded koboldcpp binaries for significantly faster operation and reduced RAM+HDD utilization (up to ~4GB less RAM usage for ROCm)
|
||||||
|
- adding presets for a basic flux or chroma image generation setup
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
Name=FriendlyKobold
|
Name=FriendlyKobold
|
||||||
Comment=A modern Electron shell for KoboldCpp
|
Comment=A modern Electron shell for KoboldCpp
|
||||||
Exec=friendly-kobold %U
|
Exec=friendly-kobold %U
|
||||||
Icon=/home/eggy/Projects/friendly-kobold/assets/icon_512.png
|
Icon=/home/eggy/Projects/friendly-kobold/assets/icon.png
|
||||||
Type=Application
|
Type=Application
|
||||||
Categories=Development;TextEditor;
|
Categories=Development;TextEditor;
|
||||||
StartupWMClass=FriendlyKobold
|
StartupWMClass=FriendlyKobold
|
||||||
|
|
|
||||||
BIN
assets/icon.png
BIN
assets/icon.png
Binary file not shown.
|
Before Width: | Height: | Size: 429 B After Width: | Height: | Size: 4.7 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.1 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 4.7 KiB |
16
cspell.json
16
cspell.json
|
|
@ -18,10 +18,12 @@
|
||||||
"babel",
|
"babel",
|
||||||
"basename",
|
"basename",
|
||||||
"bgcolor",
|
"bgcolor",
|
||||||
|
"BLAS",
|
||||||
"browserWindow",
|
"browserWindow",
|
||||||
"bundler",
|
"bundler",
|
||||||
"bundling",
|
"bundling",
|
||||||
"can",
|
"can",
|
||||||
|
"ckpt",
|
||||||
"classList",
|
"classList",
|
||||||
"className",
|
"className",
|
||||||
"clblast",
|
"clblast",
|
||||||
|
|
@ -43,6 +45,7 @@
|
||||||
"createElement",
|
"createElement",
|
||||||
"createRef",
|
"createRef",
|
||||||
"css",
|
"css",
|
||||||
|
"cublas",
|
||||||
"cuda",
|
"cuda",
|
||||||
"dataset",
|
"dataset",
|
||||||
"deps",
|
"deps",
|
||||||
|
|
@ -60,6 +63,7 @@
|
||||||
"filenames",
|
"filenames",
|
||||||
"filepath",
|
"filepath",
|
||||||
"filepaths",
|
"filepaths",
|
||||||
|
"finetuned",
|
||||||
"flashattention",
|
"flashattention",
|
||||||
"Flashattention",
|
"Flashattention",
|
||||||
"flexbox",
|
"flexbox",
|
||||||
|
|
@ -79,9 +83,11 @@
|
||||||
"GGUF",
|
"GGUF",
|
||||||
"gif",
|
"gif",
|
||||||
"gitignore",
|
"gitignore",
|
||||||
|
"gpuid",
|
||||||
"gpulayers",
|
"gpulayers",
|
||||||
"gridcol",
|
"gridcol",
|
||||||
"gridrow",
|
"gridrow",
|
||||||
|
"hipblas",
|
||||||
"hostname",
|
"hostname",
|
||||||
"html",
|
"html",
|
||||||
"http",
|
"http",
|
||||||
|
|
@ -112,11 +118,14 @@
|
||||||
"lineheight",
|
"lineheight",
|
||||||
"linted",
|
"linted",
|
||||||
"localhost",
|
"localhost",
|
||||||
|
"lowvram",
|
||||||
|
"Lowvram",
|
||||||
"lscpu",
|
"lscpu",
|
||||||
"machdep",
|
"machdep",
|
||||||
"mainWindow",
|
"mainWindow",
|
||||||
"minified",
|
"minified",
|
||||||
"minify",
|
"minify",
|
||||||
|
"mmq",
|
||||||
"moz",
|
"moz",
|
||||||
"multiplayer",
|
"multiplayer",
|
||||||
"multiuser",
|
"multiuser",
|
||||||
|
|
@ -128,6 +137,7 @@
|
||||||
"nocuda",
|
"nocuda",
|
||||||
"nodeIntegration",
|
"nodeIntegration",
|
||||||
"noheader",
|
"noheader",
|
||||||
|
"nommq",
|
||||||
"noshift",
|
"noshift",
|
||||||
"Noshift",
|
"Noshift",
|
||||||
"nsis",
|
"nsis",
|
||||||
|
|
@ -153,6 +163,8 @@
|
||||||
"preventDefault",
|
"preventDefault",
|
||||||
"PYTHONDONTWRITEBYTECODE",
|
"PYTHONDONTWRITEBYTECODE",
|
||||||
"PYTHONUNBUFFERED",
|
"PYTHONUNBUFFERED",
|
||||||
|
"quantmatmul",
|
||||||
|
"Quantmatmul",
|
||||||
"querySelector",
|
"querySelector",
|
||||||
"querySelectorAll",
|
"querySelectorAll",
|
||||||
"radeon",
|
"radeon",
|
||||||
|
|
@ -175,6 +187,8 @@
|
||||||
"sdapi",
|
"sdapi",
|
||||||
"sdclipg",
|
"sdclipg",
|
||||||
"sdclipl",
|
"sdclipl",
|
||||||
|
"sdlora",
|
||||||
|
"Sdlora",
|
||||||
"sdmodel",
|
"sdmodel",
|
||||||
"sdphotomaker",
|
"sdphotomaker",
|
||||||
"sdt5xxl",
|
"sdt5xxl",
|
||||||
|
|
@ -216,6 +230,7 @@
|
||||||
"ttf",
|
"ttf",
|
||||||
"typeof",
|
"typeof",
|
||||||
"typescript",
|
"typescript",
|
||||||
|
"unquantized",
|
||||||
"uri",
|
"uri",
|
||||||
"uris",
|
"uris",
|
||||||
"url",
|
"url",
|
||||||
|
|
@ -237,6 +252,7 @@
|
||||||
"vite",
|
"vite",
|
||||||
"vitejs",
|
"vitejs",
|
||||||
"vitest",
|
"vitest",
|
||||||
|
"vram",
|
||||||
"vulkan",
|
"vulkan",
|
||||||
"vulkaninfo",
|
"vulkaninfo",
|
||||||
"wayland",
|
"wayland",
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta
|
<meta
|
||||||
http-equiv="Content-Security-Policy"
|
http-equiv="Content-Security-Policy"
|
||||||
content="default-src 'self' 'unsafe-inline'; connect-src 'self' http://localhost:*; script-src 'self' 'unsafe-inline'; frame-src 'self' http://localhost:*;"
|
content="default-src 'self'; connect-src 'self' http: https:; script-src 'self'; style-src 'self' 'unsafe-inline' data:; frame-src 'self' http: https:;"
|
||||||
/>
|
/>
|
||||||
<title>Friendly Kobold</title>
|
<title>Friendly Kobold</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
||||||
13
package.json
13
package.json
|
|
@ -15,16 +15,12 @@
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "electron-vite dev",
|
"dev": "electron-vite dev",
|
||||||
"build": "electron-vite build && electron-builder",
|
"build": "electron-vite build",
|
||||||
"build:analyze": "cross-env ANALYZE=true electron-vite build && yarn exec open-cli dist/stats.html",
|
"package": "electron-vite build && electron-builder",
|
||||||
"build:electron": "electron-vite build",
|
"analyze": "cross-env ANALYZE=true electron-vite build && yarn dlx open-cli dist/stats.html",
|
||||||
"analyze": "yarn run build:analyze",
|
|
||||||
"analyze:server": "cross-env ANALYZE=server electron-vite build && yarn exec open-cli dist/stats.html",
|
|
||||||
"preview": "electron-vite preview",
|
"preview": "electron-vite preview",
|
||||||
"preb": "electron-vite build",
|
|
||||||
"start": "electron .",
|
"start": "electron .",
|
||||||
"electron": "wait-on tcp:5173 && electron .",
|
"electron": "wait-on tcp:5173 && electron .",
|
||||||
"dist": "electron-vite build && electron-builder",
|
|
||||||
"format": "prettier --write . --ignore-path .gitignore",
|
"format": "prettier --write . --ignore-path .gitignore",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"lint:fix": "eslint . --fix",
|
"lint:fix": "eslint . --fix",
|
||||||
|
|
@ -97,7 +93,8 @@
|
||||||
"appId": "com.friendly-kobold.app",
|
"appId": "com.friendly-kobold.app",
|
||||||
"productName": "Friendly Kobold",
|
"productName": "Friendly Kobold",
|
||||||
"compression": "maximum",
|
"compression": "maximum",
|
||||||
"icon": "assets/icon_512.png",
|
"icon": "assets/icon.png",
|
||||||
|
"publish": null,
|
||||||
"directories": {
|
"directories": {
|
||||||
"output": "release"
|
"output": "release"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,11 @@ export const useLaunchConfig = () => {
|
||||||
const [gpuLayers, setGpuLayers] = useState<number>(0);
|
const [gpuLayers, setGpuLayers] = useState<number>(0);
|
||||||
const [autoGpuLayers, setAutoGpuLayers] = useState<boolean>(false);
|
const [autoGpuLayers, setAutoGpuLayers] = useState<boolean>(false);
|
||||||
const [contextSize, setContextSize] = useState<number>(2048);
|
const [contextSize, setContextSize] = useState<number>(2048);
|
||||||
const [modelPath, setModelPath] = useState<string>('');
|
const [modelPath, setModelPath] = useState<string>(
|
||||||
|
'https://huggingface.co/MaziyarPanahi/gemma-3-4b-it-GGUF/resolve/main/gemma-3-4b-it.Q8_0.gguf?download=true'
|
||||||
|
);
|
||||||
const [additionalArguments, setAdditionalArguments] = useState<string>('');
|
const [additionalArguments, setAdditionalArguments] = useState<string>('');
|
||||||
const [port, setPort] = useState<number>(5001);
|
const [port, setPort] = useState<number | undefined>(undefined);
|
||||||
const [host, setHost] = useState<string>('localhost');
|
const [host, setHost] = useState<string>('localhost');
|
||||||
const [multiuser, setMultiuser] = useState<boolean>(false);
|
const [multiuser, setMultiuser] = useState<boolean>(false);
|
||||||
const [multiplayer, setMultiplayer] = useState<boolean>(false);
|
const [multiplayer, setMultiplayer] = useState<boolean>(false);
|
||||||
|
|
@ -22,20 +24,18 @@ export const useLaunchConfig = () => {
|
||||||
const [flashattention, setFlashattention] = useState<boolean>(false);
|
const [flashattention, setFlashattention] = useState<boolean>(false);
|
||||||
const [noavx2, setNoavx2] = useState<boolean>(false);
|
const [noavx2, setNoavx2] = useState<boolean>(false);
|
||||||
const [failsafe, setFailsafe] = useState<boolean>(false);
|
const [failsafe, setFailsafe] = useState<boolean>(false);
|
||||||
|
const [lowvram, setLowvram] = useState<boolean>(false);
|
||||||
|
const [quantmatmul, setQuantmatmul] = useState<boolean>(false);
|
||||||
const [backend, setBackend] = useState<string>('cpu');
|
const [backend, setBackend] = useState<string>('cpu');
|
||||||
|
const [gpuDevice, setGpuDevice] = useState<number>(0);
|
||||||
|
|
||||||
const [sdmodel, setSdmodel] = useState<string>('');
|
const [sdmodel, setSdmodel] = useState<string>('');
|
||||||
const [sdt5xxl, setSdt5xxl] = useState<string>(
|
const [sdt5xxl, setSdt5xxl] = useState<string>('');
|
||||||
'https://huggingface.co/camenduru/FLUX.1-dev/resolve/main/t5xxl_fp8_e4m3fn.safetensors?download=true'
|
const [sdclipl, setSdclipl] = useState<string>('');
|
||||||
);
|
|
||||||
const [sdclipl, setSdclipl] = useState<string>(
|
|
||||||
'https://huggingface.co/camenduru/FLUX.1-dev/resolve/main/clip_l.safetensors?download=true'
|
|
||||||
);
|
|
||||||
const [sdclipg, setSdclipg] = useState<string>('');
|
const [sdclipg, setSdclipg] = useState<string>('');
|
||||||
const [sdphotomaker, setSdphotomaker] = useState<string>('');
|
const [sdphotomaker, setSdphotomaker] = useState<string>('');
|
||||||
const [sdvae, setSdvae] = useState<string>(
|
const [sdvae, setSdvae] = useState<string>('');
|
||||||
'https://huggingface.co/camenduru/FLUX.1-dev/resolve/main/ae.safetensors?download=true'
|
const [sdlora, setSdlora] = 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) => {
|
||||||
|
|
@ -62,7 +62,7 @@ export const useLaunchConfig = () => {
|
||||||
if (typeof configData.port === 'number') {
|
if (typeof configData.port === 'number') {
|
||||||
setPort(configData.port);
|
setPort(configData.port);
|
||||||
} else {
|
} else {
|
||||||
setPort(5001);
|
setPort(undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof configData.host === 'string') {
|
if (typeof configData.host === 'string') {
|
||||||
|
|
@ -125,9 +125,27 @@ export const useLaunchConfig = () => {
|
||||||
setFailsafe(false);
|
setFailsafe(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof configData.lowvram === 'boolean') {
|
||||||
|
setLowvram(configData.lowvram);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof configData.quantmatmul === 'boolean') {
|
||||||
|
setQuantmatmul(configData.quantmatmul);
|
||||||
|
}
|
||||||
|
|
||||||
if (configData.usecuda !== null && configData.usecuda !== undefined) {
|
if (configData.usecuda !== null && configData.usecuda !== undefined) {
|
||||||
const gpuInfo = await window.electronAPI.kobold.detectGPU();
|
const gpuInfo = await window.electronAPI.kobold.detectGPU();
|
||||||
setBackend(gpuInfo.hasNVIDIA ? 'cuda' : 'rocm');
|
setBackend(gpuInfo.hasNVIDIA ? 'cuda' : 'rocm');
|
||||||
|
|
||||||
|
if (
|
||||||
|
Array.isArray(configData.usecuda) &&
|
||||||
|
configData.usecuda.length >= 3
|
||||||
|
) {
|
||||||
|
const [vramMode, deviceId, mmqMode] = configData.usecuda;
|
||||||
|
setLowvram(vramMode === 'lowvram');
|
||||||
|
setGpuDevice(parseInt(deviceId, 10) || 0);
|
||||||
|
setQuantmatmul(mmqMode === 'mmq');
|
||||||
|
}
|
||||||
} else if (
|
} else if (
|
||||||
configData.usevulkan !== null &&
|
configData.usevulkan !== null &&
|
||||||
configData.usevulkan !== undefined
|
configData.usevulkan !== undefined
|
||||||
|
|
@ -165,11 +183,15 @@ export const useLaunchConfig = () => {
|
||||||
if (typeof configData.sdvae === 'string') {
|
if (typeof configData.sdvae === 'string') {
|
||||||
setSdvae(configData.sdvae);
|
setSdvae(configData.sdvae);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof configData.sdlora === 'string') {
|
||||||
|
setSdlora(configData.sdlora);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const cpuCapabilities = await window.electronAPI.kobold.detectCPU();
|
const cpuCapabilities = await window.electronAPI.kobold.detectCPU();
|
||||||
setGpuLayers(0);
|
setGpuLayers(0);
|
||||||
setContextSize(2048);
|
setContextSize(2048);
|
||||||
setPort(5001);
|
setPort(undefined);
|
||||||
setHost('localhost');
|
setHost('localhost');
|
||||||
setMultiuser(false);
|
setMultiuser(false);
|
||||||
setMultiplayer(false);
|
setMultiplayer(false);
|
||||||
|
|
@ -203,7 +225,7 @@ export const useLaunchConfig = () => {
|
||||||
setModelPath('');
|
setModelPath('');
|
||||||
setGpuLayers(0);
|
setGpuLayers(0);
|
||||||
setContextSize(2048);
|
setContextSize(2048);
|
||||||
setPort(5001);
|
setPort(undefined);
|
||||||
setHost('localhost');
|
setHost('localhost');
|
||||||
setMultiuser(false);
|
setMultiuser(false);
|
||||||
setMultiplayer(false);
|
setMultiplayer(false);
|
||||||
|
|
@ -292,7 +314,7 @@ export const useLaunchConfig = () => {
|
||||||
setAutoGpuLayers(checked);
|
setAutoGpuLayers(checked);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handlePortChange = useCallback((value: number) => {
|
const handlePortChange = useCallback((value: number | undefined) => {
|
||||||
setPort(value);
|
setPort(value);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
@ -336,10 +358,22 @@ export const useLaunchConfig = () => {
|
||||||
setFailsafe(checked);
|
setFailsafe(checked);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleLowvramChange = useCallback((checked: boolean) => {
|
||||||
|
setLowvram(checked);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleQuantmatmulChange = useCallback((checked: boolean) => {
|
||||||
|
setQuantmatmul(checked);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleBackendChange = useCallback((backend: string) => {
|
const handleBackendChange = useCallback((backend: string) => {
|
||||||
setBackend(backend);
|
setBackend(backend);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleGpuDeviceChange = useCallback((device: number) => {
|
||||||
|
setGpuDevice(device);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleSdmodelChange = useCallback((path: string) => {
|
const handleSdmodelChange = useCallback((path: string) => {
|
||||||
setSdmodel(path);
|
setSdmodel(path);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
@ -406,6 +440,17 @@ export const useLaunchConfig = () => {
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleSdloraChange = useCallback((path: string) => {
|
||||||
|
setSdlora(path);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleSelectSdloraFile = useCallback(async () => {
|
||||||
|
const filePath = await window.electronAPI.kobold.selectModelFile();
|
||||||
|
if (filePath) {
|
||||||
|
setSdlora(filePath);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
const applyImageModelPreset = useCallback((preset: ImageModelPreset) => {
|
const applyImageModelPreset = useCallback((preset: ImageModelPreset) => {
|
||||||
setSdmodel(preset.sdmodel);
|
setSdmodel(preset.sdmodel);
|
||||||
setSdt5xxl(preset.sdt5xxl);
|
setSdt5xxl(preset.sdt5xxl);
|
||||||
|
|
@ -442,13 +487,17 @@ export const useLaunchConfig = () => {
|
||||||
flashattention,
|
flashattention,
|
||||||
noavx2,
|
noavx2,
|
||||||
failsafe,
|
failsafe,
|
||||||
|
lowvram,
|
||||||
|
quantmatmul,
|
||||||
backend,
|
backend,
|
||||||
|
gpuDevice,
|
||||||
sdmodel,
|
sdmodel,
|
||||||
sdt5xxl,
|
sdt5xxl,
|
||||||
sdclipl,
|
sdclipl,
|
||||||
sdclipg,
|
sdclipg,
|
||||||
sdphotomaker,
|
sdphotomaker,
|
||||||
sdvae,
|
sdvae,
|
||||||
|
sdlora,
|
||||||
|
|
||||||
parseAndApplyConfigFile,
|
parseAndApplyConfigFile,
|
||||||
loadSavedSettings,
|
loadSavedSettings,
|
||||||
|
|
@ -470,7 +519,10 @@ export const useLaunchConfig = () => {
|
||||||
handleFlashattentionChange,
|
handleFlashattentionChange,
|
||||||
handleNoavx2Change,
|
handleNoavx2Change,
|
||||||
handleFailsafeChange,
|
handleFailsafeChange,
|
||||||
|
handleLowvramChange,
|
||||||
|
handleQuantmatmulChange,
|
||||||
handleBackendChange,
|
handleBackendChange,
|
||||||
|
handleGpuDeviceChange,
|
||||||
handleSdmodelChange,
|
handleSdmodelChange,
|
||||||
handleSelectSdmodelFile,
|
handleSelectSdmodelFile,
|
||||||
handleSdt5xxlChange,
|
handleSdt5xxlChange,
|
||||||
|
|
@ -483,6 +535,8 @@ export const useLaunchConfig = () => {
|
||||||
handleSelectSdphotomakerFile,
|
handleSelectSdphotomakerFile,
|
||||||
handleSdvaeChange,
|
handleSdvaeChange,
|
||||||
handleSelectSdvaeFile,
|
handleSelectSdvaeFile,
|
||||||
|
handleSdloraChange,
|
||||||
|
handleSelectSdloraFile,
|
||||||
applyImageModelPreset,
|
applyImageModelPreset,
|
||||||
handleApplyPreset,
|
handleApplyPreset,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import { ConfigManager } from '@/main/managers/ConfigManager';
|
||||||
import { KoboldCppManager } from '@/main/managers/KoboldCppManager';
|
import { KoboldCppManager } from '@/main/managers/KoboldCppManager';
|
||||||
import { GitHubService } from '@/main/services/GitHubService';
|
import { GitHubService } from '@/main/services/GitHubService';
|
||||||
import { HardwareService } from '@/main/services/HardwareService';
|
import { HardwareService } from '@/main/services/HardwareService';
|
||||||
|
import { BinaryService } from '@/main/services/BinaryService';
|
||||||
import { IPCHandlers } from '@/main/utils/IPCHandlers';
|
import { IPCHandlers } from '@/main/utils/IPCHandlers';
|
||||||
import { APP_NAME, CONFIG_FILE_NAME } from '@/constants';
|
import { APP_NAME, CONFIG_FILE_NAME } from '@/constants';
|
||||||
|
|
||||||
|
|
@ -17,6 +18,7 @@ class FriendlyKoboldApp {
|
||||||
private koboldManager: KoboldCppManager;
|
private koboldManager: KoboldCppManager;
|
||||||
private githubService: GitHubService;
|
private githubService: GitHubService;
|
||||||
private hardwareService: HardwareService;
|
private hardwareService: HardwareService;
|
||||||
|
private binaryService: BinaryService;
|
||||||
private ipcHandlers: IPCHandlers;
|
private ipcHandlers: IPCHandlers;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
@ -26,6 +28,7 @@ class FriendlyKoboldApp {
|
||||||
this.windowManager = new WindowManager(this.configManager);
|
this.windowManager = new WindowManager(this.configManager);
|
||||||
this.githubService = new GitHubService();
|
this.githubService = new GitHubService();
|
||||||
this.hardwareService = new HardwareService();
|
this.hardwareService = new HardwareService();
|
||||||
|
this.binaryService = new BinaryService();
|
||||||
this.koboldManager = new KoboldCppManager(
|
this.koboldManager = new KoboldCppManager(
|
||||||
this.configManager,
|
this.configManager,
|
||||||
this.githubService,
|
this.githubService,
|
||||||
|
|
@ -35,7 +38,8 @@ class FriendlyKoboldApp {
|
||||||
this.koboldManager,
|
this.koboldManager,
|
||||||
this.configManager,
|
this.configManager,
|
||||||
this.githubService,
|
this.githubService,
|
||||||
this.hardwareService
|
this.hardwareService,
|
||||||
|
this.binaryService
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
124
src/main/services/BinaryService.ts
Normal file
124
src/main/services/BinaryService.ts
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
import { existsSync } from 'fs';
|
||||||
|
import { join, dirname } from 'path';
|
||||||
|
|
||||||
|
export interface BackendSupport {
|
||||||
|
rocm: boolean;
|
||||||
|
vulkan: boolean;
|
||||||
|
clblast: boolean;
|
||||||
|
noavx2: boolean;
|
||||||
|
failsafe: boolean;
|
||||||
|
cuda: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BinaryService {
|
||||||
|
private backendSupportCache = new Map<string, BackendSupport>();
|
||||||
|
|
||||||
|
detectBackendSupport(koboldBinaryPath: string): BackendSupport {
|
||||||
|
if (this.backendSupportCache.has(koboldBinaryPath)) {
|
||||||
|
return this.backendSupportCache.get(koboldBinaryPath)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
const support: BackendSupport = {
|
||||||
|
rocm: false,
|
||||||
|
vulkan: false,
|
||||||
|
clblast: false,
|
||||||
|
noavx2: false,
|
||||||
|
failsafe: false,
|
||||||
|
cuda: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const binaryDir = dirname(koboldBinaryPath);
|
||||||
|
const internalDir = join(binaryDir, '_internal');
|
||||||
|
|
||||||
|
if (!existsSync(internalDir)) {
|
||||||
|
console.warn(
|
||||||
|
'_internal directory not found, cannot detect backend support'
|
||||||
|
);
|
||||||
|
this.backendSupportCache.set(koboldBinaryPath, support);
|
||||||
|
return support;
|
||||||
|
}
|
||||||
|
|
||||||
|
const platform = process.platform;
|
||||||
|
const isDynamicLib = (name: string) => {
|
||||||
|
if (platform === 'win32') {
|
||||||
|
return existsSync(join(internalDir, `${name}.dll`));
|
||||||
|
} else {
|
||||||
|
return existsSync(join(internalDir, `${name}.so`));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
support.rocm = isDynamicLib('koboldcpp_hipblas');
|
||||||
|
support.vulkan = isDynamicLib('koboldcpp_vulkan');
|
||||||
|
support.clblast = isDynamicLib('koboldcpp_clblast');
|
||||||
|
support.noavx2 = isDynamicLib('koboldcpp_noavx2');
|
||||||
|
support.failsafe = isDynamicLib('koboldcpp_failsafe');
|
||||||
|
support.cuda = isDynamicLib('koboldcpp_cublas');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error detecting backend support:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.backendSupportCache.set(koboldBinaryPath, support);
|
||||||
|
return support;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAvailableBackends(
|
||||||
|
koboldBinaryPath: string,
|
||||||
|
hardwareCapabilities?: {
|
||||||
|
cuda: { supported: boolean; devices: string[] };
|
||||||
|
rocm: { supported: boolean; devices: string[] };
|
||||||
|
vulkan: { supported: boolean; devices: string[] };
|
||||||
|
clblast: { supported: boolean; devices: string[] };
|
||||||
|
}
|
||||||
|
): Array<{ value: string; label: string; devices?: string[] }> {
|
||||||
|
const backendSupport = this.detectBackendSupport(koboldBinaryPath);
|
||||||
|
const backends: Array<{
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
devices?: string[];
|
||||||
|
}> = [];
|
||||||
|
|
||||||
|
if (backendSupport.cuda && hardwareCapabilities?.cuda.supported) {
|
||||||
|
backends.push({
|
||||||
|
value: 'cuda',
|
||||||
|
label: 'CUDA',
|
||||||
|
devices: hardwareCapabilities.cuda.devices,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (backendSupport.rocm && hardwareCapabilities?.rocm.supported) {
|
||||||
|
backends.push({
|
||||||
|
value: 'rocm',
|
||||||
|
label: 'ROCm',
|
||||||
|
devices: hardwareCapabilities.rocm.devices,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (backendSupport.vulkan && hardwareCapabilities?.vulkan.supported) {
|
||||||
|
backends.push({
|
||||||
|
value: 'vulkan',
|
||||||
|
label: 'Vulkan',
|
||||||
|
devices: hardwareCapabilities.vulkan.devices,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (backendSupport.clblast && hardwareCapabilities?.clblast.supported) {
|
||||||
|
backends.push({
|
||||||
|
value: 'clblast',
|
||||||
|
label: 'CLBlast',
|
||||||
|
devices: hardwareCapabilities.clblast.devices,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
backends.push({
|
||||||
|
value: 'cpu',
|
||||||
|
label: 'CPU',
|
||||||
|
});
|
||||||
|
|
||||||
|
return backends;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearCache(): void {
|
||||||
|
this.backendSupportCache.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -20,15 +20,9 @@ export class HardwareService {
|
||||||
try {
|
try {
|
||||||
const [cpu, flags] = await Promise.all([si.cpu(), si.cpuFlags()]);
|
const [cpu, flags] = await Promise.all([si.cpu(), si.cpuFlags()]);
|
||||||
|
|
||||||
const cpuInfo: string[] = [];
|
const devices: string[] = [];
|
||||||
if (cpu.brand) {
|
if (cpu.brand) {
|
||||||
cpuInfo.push(cpu.brand);
|
devices.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 avx = flags.includes('avx') || flags.includes('AVX');
|
||||||
|
|
@ -37,7 +31,7 @@ export class HardwareService {
|
||||||
this.cpuCapabilitiesCache = {
|
this.cpuCapabilitiesCache = {
|
||||||
avx,
|
avx,
|
||||||
avx2,
|
avx2,
|
||||||
cpuInfo: cpuInfo.length > 0 ? cpuInfo : ['CPU information unavailable'],
|
devices,
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.cpuCapabilitiesCache;
|
return this.cpuCapabilitiesCache;
|
||||||
|
|
@ -46,7 +40,7 @@ export class HardwareService {
|
||||||
const fallbackCapabilities = {
|
const fallbackCapabilities = {
|
||||||
avx: false,
|
avx: false,
|
||||||
avx2: false,
|
avx2: false,
|
||||||
cpuInfo: ['CPU detection failed'],
|
devices: [],
|
||||||
};
|
};
|
||||||
this.cpuCapabilitiesCache = fallbackCapabilities;
|
this.cpuCapabilitiesCache = fallbackCapabilities;
|
||||||
return fallbackCapabilities;
|
return fallbackCapabilities;
|
||||||
|
|
|
||||||
|
|
@ -4,23 +4,28 @@ import { KoboldCppManager } from '@/main/managers/KoboldCppManager';
|
||||||
import { ConfigManager } from '@/main/managers/ConfigManager';
|
import { ConfigManager } from '@/main/managers/ConfigManager';
|
||||||
import { GitHubService } from '@/main/services/GitHubService';
|
import { GitHubService } from '@/main/services/GitHubService';
|
||||||
import { HardwareService } from '@/main/services/HardwareService';
|
import { HardwareService } from '@/main/services/HardwareService';
|
||||||
|
import { BinaryService } from '@/main/services/BinaryService';
|
||||||
|
import type { GPUCapabilities } from '@/types/hardware';
|
||||||
|
|
||||||
export class IPCHandlers {
|
export class IPCHandlers {
|
||||||
private koboldManager: KoboldCppManager;
|
private koboldManager: KoboldCppManager;
|
||||||
private configManager: ConfigManager;
|
private configManager: ConfigManager;
|
||||||
private githubService: GitHubService;
|
private githubService: GitHubService;
|
||||||
private hardwareService: HardwareService;
|
private hardwareService: HardwareService;
|
||||||
|
private binaryService: BinaryService;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
koboldManager: KoboldCppManager,
|
koboldManager: KoboldCppManager,
|
||||||
configManager: ConfigManager,
|
configManager: ConfigManager,
|
||||||
githubService: GitHubService,
|
githubService: GitHubService,
|
||||||
hardwareService: HardwareService
|
hardwareService: HardwareService,
|
||||||
|
binaryService: BinaryService
|
||||||
) {
|
) {
|
||||||
this.koboldManager = koboldManager;
|
this.koboldManager = koboldManager;
|
||||||
this.configManager = configManager;
|
this.configManager = configManager;
|
||||||
this.githubService = githubService;
|
this.githubService = githubService;
|
||||||
this.hardwareService = hardwareService;
|
this.hardwareService = hardwareService;
|
||||||
|
this.binaryService = binaryService;
|
||||||
}
|
}
|
||||||
|
|
||||||
setupHandlers() {
|
setupHandlers() {
|
||||||
|
|
@ -132,6 +137,23 @@ export class IPCHandlers {
|
||||||
this.hardwareService.detectAllWithCapabilities()
|
this.hardwareService.detectAllWithCapabilities()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ipcMain.handle('kobold:detectBackendSupport', (_, binaryPath: string) =>
|
||||||
|
this.binaryService.detectBackendSupport(binaryPath)
|
||||||
|
);
|
||||||
|
|
||||||
|
ipcMain.handle(
|
||||||
|
'kobold:getAvailableBackends',
|
||||||
|
(_, binaryPath: string, hardwareCapabilities: GPUCapabilities) =>
|
||||||
|
this.binaryService.getAvailableBackends(
|
||||||
|
binaryPath,
|
||||||
|
hardwareCapabilities
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
ipcMain.handle('kobold:clearBinaryCache', () =>
|
||||||
|
this.binaryService.clearCache()
|
||||||
|
);
|
||||||
|
|
||||||
ipcMain.handle('kobold:getPlatform', () => ({
|
ipcMain.handle('kobold:getPlatform', () => ({
|
||||||
platform: process.platform,
|
platform: process.platform,
|
||||||
arch: process.arch,
|
arch: process.arch,
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import type {
|
||||||
ConfigAPI,
|
ConfigAPI,
|
||||||
UpdateInfo,
|
UpdateInfo,
|
||||||
} from '@/types/electron';
|
} from '@/types/electron';
|
||||||
|
import type { GPUCapabilities } from '@/types/hardware';
|
||||||
|
|
||||||
const koboldAPI: KoboldAPI = {
|
const koboldAPI: KoboldAPI = {
|
||||||
getInstalledVersion: () => ipcRenderer.invoke('kobold:getInstalledVersion'),
|
getInstalledVersion: () => ipcRenderer.invoke('kobold:getInstalledVersion'),
|
||||||
|
|
@ -30,6 +31,18 @@ const koboldAPI: KoboldAPI = {
|
||||||
detectHardware: () => ipcRenderer.invoke('kobold:detectHardware'),
|
detectHardware: () => ipcRenderer.invoke('kobold:detectHardware'),
|
||||||
detectAllCapabilities: () =>
|
detectAllCapabilities: () =>
|
||||||
ipcRenderer.invoke('kobold:detectAllCapabilities'),
|
ipcRenderer.invoke('kobold:detectAllCapabilities'),
|
||||||
|
detectBackendSupport: (binaryPath: string) =>
|
||||||
|
ipcRenderer.invoke('kobold:detectBackendSupport', binaryPath),
|
||||||
|
getAvailableBackends: (
|
||||||
|
binaryPath: string,
|
||||||
|
hardwareCapabilities: GPUCapabilities
|
||||||
|
) =>
|
||||||
|
ipcRenderer.invoke(
|
||||||
|
'kobold:getAvailableBackends',
|
||||||
|
binaryPath,
|
||||||
|
hardwareCapabilities
|
||||||
|
),
|
||||||
|
clearBinaryCache: () => ipcRenderer.invoke('kobold:clearBinaryCache'),
|
||||||
getCurrentInstallDir: () => ipcRenderer.invoke('kobold:getCurrentInstallDir'),
|
getCurrentInstallDir: () => ipcRenderer.invoke('kobold:getCurrentInstallDir'),
|
||||||
selectInstallDirectory: () =>
|
selectInstallDirectory: () =>
|
||||||
ipcRenderer.invoke('kobold:selectInstallDirectory'),
|
ipcRenderer.invoke('kobold:selectInstallDirectory'),
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { Stack, Text, Group, TextInput, Checkbox } from '@mantine/core';
|
import { Stack, Text, Group, TextInput, Checkbox } from '@mantine/core';
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
import { InfoTooltip } from '@/components/InfoTooltip';
|
import { InfoTooltip } from '@/components/InfoTooltip';
|
||||||
|
|
||||||
interface AdvancedTabProps {
|
interface AdvancedTabProps {
|
||||||
|
|
@ -7,11 +8,16 @@ interface AdvancedTabProps {
|
||||||
flashattention: boolean;
|
flashattention: boolean;
|
||||||
noavx2: boolean;
|
noavx2: boolean;
|
||||||
failsafe: boolean;
|
failsafe: boolean;
|
||||||
|
lowvram: boolean;
|
||||||
|
quantmatmul: boolean;
|
||||||
|
backend: string;
|
||||||
onAdditionalArgumentsChange: (args: string) => void;
|
onAdditionalArgumentsChange: (args: string) => void;
|
||||||
onNoshiftChange: (noshift: boolean) => void;
|
onNoshiftChange: (noshift: boolean) => void;
|
||||||
onFlashattentionChange: (flashattention: boolean) => void;
|
onFlashattentionChange: (flashattention: boolean) => void;
|
||||||
onNoavx2Change: (noavx2: boolean) => void;
|
onNoavx2Change: (noavx2: boolean) => void;
|
||||||
onFailsafeChange: (failsafe: boolean) => void;
|
onFailsafeChange: (failsafe: boolean) => void;
|
||||||
|
onLowvramChange: (lowvram: boolean) => void;
|
||||||
|
onQuantmatmulChange: (quantmatmul: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AdvancedTab = ({
|
export const AdvancedTab = ({
|
||||||
|
|
@ -20,14 +26,56 @@ export const AdvancedTab = ({
|
||||||
flashattention,
|
flashattention,
|
||||||
noavx2,
|
noavx2,
|
||||||
failsafe,
|
failsafe,
|
||||||
|
lowvram,
|
||||||
|
quantmatmul,
|
||||||
|
backend,
|
||||||
onAdditionalArgumentsChange,
|
onAdditionalArgumentsChange,
|
||||||
onNoshiftChange,
|
onNoshiftChange,
|
||||||
onFlashattentionChange,
|
onFlashattentionChange,
|
||||||
onNoavx2Change,
|
onNoavx2Change,
|
||||||
onFailsafeChange,
|
onFailsafeChange,
|
||||||
}: AdvancedTabProps) => (
|
onLowvramChange,
|
||||||
|
onQuantmatmulChange,
|
||||||
|
}: AdvancedTabProps) => {
|
||||||
|
const [backendSupport, setBackendSupport] = useState<{
|
||||||
|
noavx2: boolean;
|
||||||
|
failsafe: boolean;
|
||||||
|
} | null>(null);
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const detectBackendSupport = async () => {
|
||||||
|
try {
|
||||||
|
const currentBinaryInfo =
|
||||||
|
await window.electronAPI.kobold.getCurrentBinaryInfo();
|
||||||
|
if (currentBinaryInfo?.path) {
|
||||||
|
const support = await window.electronAPI.kobold.detectBackendSupport(
|
||||||
|
currentBinaryInfo.path
|
||||||
|
);
|
||||||
|
setBackendSupport({
|
||||||
|
noavx2: support.noavx2,
|
||||||
|
failsafe: support.failsafe,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to detect backend support:', error);
|
||||||
|
setBackendSupport({ noavx2: false, failsafe: false });
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void detectBackendSupport();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
<Stack gap="lg">
|
<Stack gap="lg">
|
||||||
<div>
|
<div>
|
||||||
|
<Group gap="xs" align="center" mb="md">
|
||||||
|
<Text size="sm" fw={600}>
|
||||||
|
Performance Options
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
<Group gap="lg" align="flex-start" wrap="nowrap">
|
<Group gap="lg" align="flex-start" wrap="nowrap">
|
||||||
<div style={{ minWidth: '200px' }}>
|
<div style={{ minWidth: '200px' }}>
|
||||||
|
|
@ -57,6 +105,47 @@ export const AdvancedTab = ({
|
||||||
</div>
|
</div>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
|
{(backend === 'cuda' || backend === 'rocm') && (
|
||||||
|
<Group gap="lg" align="flex-start" wrap="nowrap">
|
||||||
|
<div style={{ minWidth: '200px' }}>
|
||||||
|
<Group gap="xs" align="center">
|
||||||
|
<Checkbox
|
||||||
|
checked={lowvram}
|
||||||
|
onChange={(event) =>
|
||||||
|
onLowvramChange(event.currentTarget.checked)
|
||||||
|
}
|
||||||
|
label="Low VRAM"
|
||||||
|
/>
|
||||||
|
<InfoTooltip
|
||||||
|
label="Avoid offloading KV Cache or scratch buffers to VRAM. Allows more layers to fit, but may result in a speed loss."
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ minWidth: '200px' }}>
|
||||||
|
<Group gap="xs" align="center">
|
||||||
|
<Checkbox
|
||||||
|
checked={quantmatmul}
|
||||||
|
onChange={(event) =>
|
||||||
|
onQuantmatmulChange(event.currentTarget.checked)
|
||||||
|
}
|
||||||
|
label="QuantMatMul"
|
||||||
|
/>
|
||||||
|
<InfoTooltip label="Enable MMQ mode to use finetuned kernels instead of default CuBLAS/HipBLAS for prompt processing." />
|
||||||
|
</Group>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Group gap="xs" align="center" mb="md">
|
||||||
|
<Text size="sm" fw={600}>
|
||||||
|
Hardware Compatibility
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
<Stack gap="md">
|
||||||
<Group gap="lg" align="flex-start" wrap="nowrap">
|
<Group gap="lg" align="flex-start" wrap="nowrap">
|
||||||
<div style={{ minWidth: '200px' }}>
|
<div style={{ minWidth: '200px' }}>
|
||||||
<Group gap="xs" align="center">
|
<Group gap="xs" align="center">
|
||||||
|
|
@ -66,8 +155,15 @@ export const AdvancedTab = ({
|
||||||
onNoavx2Change(event.currentTarget.checked)
|
onNoavx2Change(event.currentTarget.checked)
|
||||||
}
|
}
|
||||||
label="Disable AVX2"
|
label="Disable AVX2"
|
||||||
|
disabled={isLoading || !backendSupport?.noavx2}
|
||||||
|
/>
|
||||||
|
<InfoTooltip
|
||||||
|
label={
|
||||||
|
!backendSupport?.noavx2 && !isLoading
|
||||||
|
? 'This binary does not support the no-AVX2 mode.'
|
||||||
|
: 'Do not use AVX2 instructions, a slower compatibility mode for older devices.'
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<InfoTooltip label="Do not use AVX2 instructions, a slower compatibility mode for older devices." />
|
|
||||||
</Group>
|
</Group>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -79,13 +175,21 @@ export const AdvancedTab = ({
|
||||||
onFailsafeChange(event.currentTarget.checked)
|
onFailsafeChange(event.currentTarget.checked)
|
||||||
}
|
}
|
||||||
label="Failsafe"
|
label="Failsafe"
|
||||||
|
disabled={isLoading || !backendSupport?.failsafe}
|
||||||
|
/>
|
||||||
|
<InfoTooltip
|
||||||
|
label={
|
||||||
|
!backendSupport?.failsafe && !isLoading
|
||||||
|
? 'This binary does not support failsafe mode.'
|
||||||
|
: 'Use failsafe mode, extremely slow CPU only compatibility mode that should work on all devices. Can be combined with useclblast if your device supports OpenCL.'
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<InfoTooltip label="Use failsafe mode, extremely slow CPU only compatibility mode that should work on all devices. Can be combined with useclblast if your device supports OpenCL." />
|
|
||||||
</Group>
|
</Group>
|
||||||
</div>
|
</div>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Group gap="xs" align="center" mb="xs">
|
<Group gap="xs" align="center" mb="xs">
|
||||||
<Text size="sm" fw={500}>
|
<Text size="sm" fw={500}>
|
||||||
|
|
@ -103,3 +207,4 @@ export const AdvancedTab = ({
|
||||||
</div>
|
</div>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,12 @@ import {
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { AlertTriangle } from 'lucide-react';
|
import { AlertTriangle } from 'lucide-react';
|
||||||
import { InfoTooltip } from '@/components/InfoTooltip';
|
import { InfoTooltip } from '@/components/InfoTooltip';
|
||||||
import { isNoCudaBinary, isRocmBinary } from '@/utils/binaryUtils';
|
|
||||||
|
|
||||||
interface BackendSelectorProps {
|
interface BackendSelectorProps {
|
||||||
backend: string;
|
backend: string;
|
||||||
onBackendChange: (backend: string) => void;
|
onBackendChange: (backend: string) => void;
|
||||||
|
gpuDevice?: number;
|
||||||
|
onGpuDeviceChange?: (device: number) => void;
|
||||||
noavx2?: boolean;
|
noavx2?: boolean;
|
||||||
failsafe?: boolean;
|
failsafe?: boolean;
|
||||||
}
|
}
|
||||||
|
|
@ -23,6 +24,8 @@ interface BackendSelectorProps {
|
||||||
export const BackendSelector = ({
|
export const BackendSelector = ({
|
||||||
backend,
|
backend,
|
||||||
onBackendChange,
|
onBackendChange,
|
||||||
|
gpuDevice = 0,
|
||||||
|
onGpuDeviceChange,
|
||||||
noavx2 = false,
|
noavx2 = false,
|
||||||
failsafe = false,
|
failsafe = false,
|
||||||
}: BackendSelectorProps) => {
|
}: BackendSelectorProps) => {
|
||||||
|
|
@ -55,52 +58,22 @@ export const BackendSelector = ({
|
||||||
avx2: cpuCapabilitiesResult.avx2,
|
avx2: cpuCapabilitiesResult.avx2,
|
||||||
});
|
});
|
||||||
|
|
||||||
const backends: Array<{
|
let backends: Array<{
|
||||||
value: string;
|
value: string;
|
||||||
label: string;
|
label: string;
|
||||||
devices?: string[];
|
devices?: string[];
|
||||||
}> = [];
|
}> = [];
|
||||||
|
|
||||||
if (currentBinaryInfo?.filename) {
|
if (currentBinaryInfo?.path) {
|
||||||
const filename = currentBinaryInfo.filename;
|
backends = await window.electronAPI.kobold.getAvailableBackends(
|
||||||
|
currentBinaryInfo.path,
|
||||||
|
gpuCapabilities
|
||||||
|
);
|
||||||
|
|
||||||
if (!isNoCudaBinary(filename) && gpuCapabilities.cuda.supported) {
|
const cpuBackend = backends.find((b) => b.value === 'cpu');
|
||||||
backends.push({
|
if (cpuBackend) {
|
||||||
value: 'cuda',
|
cpuBackend.devices = cpuCapabilitiesResult.devices;
|
||||||
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: 'cpu',
|
|
||||||
label: 'CPU',
|
|
||||||
devices: cpuCapabilitiesResult.cpuInfo,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setAvailableBackends(backends);
|
setAvailableBackends(backends);
|
||||||
|
|
@ -174,18 +147,48 @@ export const BackendSelector = ({
|
||||||
: 'Select backend'
|
: 'Select backend'
|
||||||
}
|
}
|
||||||
value={backend}
|
value={backend}
|
||||||
onChange={(value) => value && onBackendChange(value)}
|
onChange={(value) => {
|
||||||
|
if (value) {
|
||||||
|
onBackendChange(value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
data={availableBackends.map((b) => ({
|
data={availableBackends.map((b) => ({
|
||||||
value: b.value,
|
value: b.value,
|
||||||
label: b.label,
|
label: b.label,
|
||||||
}))}
|
}))}
|
||||||
disabled={isLoadingBackends || availableBackends.length === 0}
|
disabled={isLoadingBackends || availableBackends.length === 0}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{(backend === 'cuda' || backend === 'rocm') &&
|
||||||
|
onGpuDeviceChange &&
|
||||||
|
availableBackends.find((b) => b.value === backend)?.devices &&
|
||||||
|
availableBackends.find((b) => b.value === backend)!.devices!.length >
|
||||||
|
1 && (
|
||||||
|
<Select
|
||||||
|
label="GPU Device"
|
||||||
|
placeholder="Select GPU device"
|
||||||
|
value={gpuDevice.toString()}
|
||||||
|
onChange={(value) =>
|
||||||
|
value && onGpuDeviceChange(parseInt(value, 10))
|
||||||
|
}
|
||||||
|
data={availableBackends
|
||||||
|
.find((b) => b.value === backend)!
|
||||||
|
.devices!.map((device, index) => ({
|
||||||
|
value: index.toString(),
|
||||||
|
label: `GPU ${index}: ${device}`,
|
||||||
|
}))}
|
||||||
|
mt="xs"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{backend &&
|
{backend &&
|
||||||
availableBackends.find((b) => b.value === backend)?.devices && (
|
availableBackends.find((b) => b.value === backend)?.devices && (
|
||||||
<Group gap="xs" mt="xs">
|
<Group gap="xs" mt="xs">
|
||||||
<Text size="xs" c="dimmed">
|
<Text size="xs" c="dimmed">
|
||||||
Devices:
|
{availableBackends.find((b) => b.value === backend)?.devices
|
||||||
|
?.length === 1
|
||||||
|
? 'Device:'
|
||||||
|
: 'Devices:'}
|
||||||
</Text>
|
</Text>
|
||||||
{availableBackends
|
{availableBackends
|
||||||
.find((b) => b.value === backend)
|
.find((b) => b.value === backend)
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ interface GeneralTabProps {
|
||||||
autoGpuLayers: boolean;
|
autoGpuLayers: boolean;
|
||||||
contextSize: number;
|
contextSize: number;
|
||||||
backend: string;
|
backend: string;
|
||||||
|
gpuDevice?: number;
|
||||||
noavx2: boolean;
|
noavx2: boolean;
|
||||||
failsafe: boolean;
|
failsafe: boolean;
|
||||||
onModelPathChange: (path: string) => void;
|
onModelPathChange: (path: string) => void;
|
||||||
|
|
@ -26,6 +27,7 @@ interface GeneralTabProps {
|
||||||
onAutoGpuLayersChange: (auto: boolean) => void;
|
onAutoGpuLayersChange: (auto: boolean) => void;
|
||||||
onContextSizeChange: (size: number) => void;
|
onContextSizeChange: (size: number) => void;
|
||||||
onBackendChange: (backend: string) => void;
|
onBackendChange: (backend: string) => void;
|
||||||
|
onGpuDeviceChange?: (device: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GeneralTab = ({
|
export const GeneralTab = ({
|
||||||
|
|
@ -34,6 +36,7 @@ export const GeneralTab = ({
|
||||||
autoGpuLayers,
|
autoGpuLayers,
|
||||||
contextSize,
|
contextSize,
|
||||||
backend,
|
backend,
|
||||||
|
gpuDevice,
|
||||||
noavx2,
|
noavx2,
|
||||||
failsafe,
|
failsafe,
|
||||||
onModelPathChange,
|
onModelPathChange,
|
||||||
|
|
@ -42,6 +45,7 @@ export const GeneralTab = ({
|
||||||
onAutoGpuLayersChange,
|
onAutoGpuLayersChange,
|
||||||
onContextSizeChange,
|
onContextSizeChange,
|
||||||
onBackendChange,
|
onBackendChange,
|
||||||
|
onGpuDeviceChange,
|
||||||
}: GeneralTabProps) => {
|
}: GeneralTabProps) => {
|
||||||
const validationState = getInputValidationState(modelPath);
|
const validationState = getInputValidationState(modelPath);
|
||||||
|
|
||||||
|
|
@ -68,6 +72,15 @@ export const GeneralTab = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack gap="lg">
|
<Stack gap="lg">
|
||||||
|
<BackendSelector
|
||||||
|
backend={backend}
|
||||||
|
onBackendChange={onBackendChange}
|
||||||
|
gpuDevice={gpuDevice}
|
||||||
|
onGpuDeviceChange={onGpuDeviceChange}
|
||||||
|
noavx2={noavx2}
|
||||||
|
failsafe={failsafe}
|
||||||
|
/>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Text size="sm" fw={500} mb="xs">
|
<Text size="sm" fw={500} mb="xs">
|
||||||
Text Model File
|
Text Model File
|
||||||
|
|
@ -105,13 +118,6 @@ export const GeneralTab = ({
|
||||||
</Group>
|
</Group>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<BackendSelector
|
|
||||||
backend={backend}
|
|
||||||
onBackendChange={onBackendChange}
|
|
||||||
noavx2={noavx2}
|
|
||||||
failsafe={failsafe}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<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">
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ interface ImageGenerationTabProps {
|
||||||
sdclipg: string;
|
sdclipg: string;
|
||||||
sdphotomaker: string;
|
sdphotomaker: string;
|
||||||
sdvae: string;
|
sdvae: string;
|
||||||
|
sdlora: string;
|
||||||
textModelPath?: string;
|
textModelPath?: string;
|
||||||
onSdmodelChange: (path: string) => void;
|
onSdmodelChange: (path: string) => void;
|
||||||
onSelectSdmodelFile: () => void;
|
onSelectSdmodelFile: () => void;
|
||||||
|
|
@ -32,6 +33,8 @@ interface ImageGenerationTabProps {
|
||||||
onSelectSdphotomakerFile: () => void;
|
onSelectSdphotomakerFile: () => void;
|
||||||
onSdvaeChange: (path: string) => void;
|
onSdvaeChange: (path: string) => void;
|
||||||
onSelectSdvaeFile: () => void;
|
onSelectSdvaeFile: () => void;
|
||||||
|
onSdloraChange: (path: string) => void;
|
||||||
|
onSelectSdloraFile: () => void;
|
||||||
onApplyPreset: (presetName: string) => void;
|
onApplyPreset: (presetName: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -125,6 +128,7 @@ export const ImageGenerationTab = ({
|
||||||
sdclipg,
|
sdclipg,
|
||||||
sdphotomaker,
|
sdphotomaker,
|
||||||
sdvae,
|
sdvae,
|
||||||
|
sdlora,
|
||||||
textModelPath,
|
textModelPath,
|
||||||
onSdmodelChange,
|
onSdmodelChange,
|
||||||
onSelectSdmodelFile,
|
onSelectSdmodelFile,
|
||||||
|
|
@ -138,6 +142,8 @@ export const ImageGenerationTab = ({
|
||||||
onSelectSdphotomakerFile,
|
onSelectSdphotomakerFile,
|
||||||
onSdvaeChange,
|
onSdvaeChange,
|
||||||
onSelectSdvaeFile,
|
onSelectSdvaeFile,
|
||||||
|
onSdloraChange,
|
||||||
|
onSelectSdloraFile,
|
||||||
onApplyPreset,
|
onApplyPreset,
|
||||||
}: ImageGenerationTabProps) => {
|
}: ImageGenerationTabProps) => {
|
||||||
const hasTextModel = textModelPath?.trim() !== '';
|
const hasTextModel = textModelPath?.trim() !== '';
|
||||||
|
|
@ -228,6 +234,15 @@ export const ImageGenerationTab = ({
|
||||||
onChange={onSdvaeChange}
|
onChange={onSdvaeChange}
|
||||||
onSelectFile={onSelectSdvaeFile}
|
onSelectFile={onSelectSdvaeFile}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<ModelField
|
||||||
|
label="Image LoRa"
|
||||||
|
value={sdlora}
|
||||||
|
placeholder="Select a LoRa file or enter a direct URL"
|
||||||
|
tooltip="LoRa (Low-Rank Adaptation) file for customizing image generation. Select a .safetensors or .gguf LoRa file to be loaded. Should be unquantized."
|
||||||
|
onChange={onSdloraChange}
|
||||||
|
onSelectFile={onSelectSdloraFile}
|
||||||
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,16 @@
|
||||||
import { Stack, Text, TextInput, Group, Checkbox } from '@mantine/core';
|
import { Stack, Text, TextInput, Group, Checkbox } from '@mantine/core';
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
import { InfoTooltip } from '@/components/InfoTooltip';
|
import { InfoTooltip } from '@/components/InfoTooltip';
|
||||||
|
|
||||||
interface NetworkTabProps {
|
interface NetworkTabProps {
|
||||||
port: number;
|
port: number | undefined;
|
||||||
host: string;
|
host: string;
|
||||||
multiuser: boolean;
|
multiuser: boolean;
|
||||||
multiplayer: boolean;
|
multiplayer: boolean;
|
||||||
remotetunnel: boolean;
|
remotetunnel: boolean;
|
||||||
nocertify: boolean;
|
nocertify: boolean;
|
||||||
websearch: boolean;
|
websearch: boolean;
|
||||||
onPortChange: (port: number) => void;
|
onPortChange: (port: number | undefined) => void;
|
||||||
onHostChange: (host: string) => void;
|
onHostChange: (host: string) => void;
|
||||||
onMultiuserChange: (multiuser: boolean) => void;
|
onMultiuserChange: (multiuser: boolean) => void;
|
||||||
onMultiplayerChange: (multiplayer: boolean) => void;
|
onMultiplayerChange: (multiplayer: boolean) => void;
|
||||||
|
|
@ -33,7 +34,14 @@ export const NetworkTab = ({
|
||||||
onRemotetunnelChange,
|
onRemotetunnelChange,
|
||||||
onNocertifyChange,
|
onNocertifyChange,
|
||||||
onWebsearchChange,
|
onWebsearchChange,
|
||||||
}: NetworkTabProps) => (
|
}: NetworkTabProps) => {
|
||||||
|
const [portInput, setPortInput] = useState(port?.toString() ?? '');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setPortInput(port?.toString() ?? '');
|
||||||
|
}, [port]);
|
||||||
|
|
||||||
|
return (
|
||||||
<Stack gap="lg">
|
<Stack gap="lg">
|
||||||
<Group gap="lg" align="flex-start">
|
<Group gap="lg" align="flex-start">
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -56,14 +64,31 @@ export const NetworkTab = ({
|
||||||
<Text size="sm" fw={500}>
|
<Text size="sm" fw={500}>
|
||||||
Port
|
Port
|
||||||
</Text>
|
</Text>
|
||||||
<InfoTooltip label="The port number on which KoboldCpp will listen for connections. Default is 5001." />
|
<InfoTooltip label="The port number on which KoboldCpp will listen for connections. Leave empty to use default port 5001." />
|
||||||
</Group>
|
</Group>
|
||||||
<TextInput
|
<TextInput
|
||||||
placeholder="5001"
|
placeholder="5001"
|
||||||
value={port.toString()}
|
value={portInput}
|
||||||
onChange={(event) =>
|
onChange={(event) => {
|
||||||
onPortChange(Number(event.currentTarget.value) || 5001)
|
const value = event.currentTarget.value;
|
||||||
|
setPortInput(value);
|
||||||
|
|
||||||
|
if (value === '') {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const numValue = Number(value);
|
||||||
|
if (!isNaN(numValue) && numValue >= 1 && numValue <= 65535) {
|
||||||
|
onPortChange(numValue);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onBlur={(event) => {
|
||||||
|
const value = event.currentTarget.value;
|
||||||
|
if (value === '') {
|
||||||
|
onPortChange(undefined);
|
||||||
|
setPortInput('');
|
||||||
|
}
|
||||||
|
}}
|
||||||
type="number"
|
type="number"
|
||||||
min={1}
|
min={1}
|
||||||
max={65535}
|
max={65535}
|
||||||
|
|
@ -133,7 +158,9 @@ export const NetworkTab = ({
|
||||||
<Group gap="xs" align="center">
|
<Group gap="xs" align="center">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={websearch}
|
checked={websearch}
|
||||||
onChange={(event) => onWebsearchChange(event.currentTarget.checked)}
|
onChange={(event) =>
|
||||||
|
onWebsearchChange(event.currentTarget.checked)
|
||||||
|
}
|
||||||
label="Enable WebSearch"
|
label="Enable WebSearch"
|
||||||
/>
|
/>
|
||||||
<InfoTooltip label="Enable the local search engine proxy so Web Searches can be done." />
|
<InfoTooltip label="Enable the local search engine proxy so Web Searches can be done." />
|
||||||
|
|
@ -142,3 +169,4 @@ export const NetworkTab = ({
|
||||||
</div>
|
</div>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
import { Modal, Stack, TextInput, Group, Button } from '@mantine/core';
|
|
||||||
import { Save } from 'lucide-react';
|
|
||||||
|
|
||||||
interface SaveConfigModalProps {
|
|
||||||
opened: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
configName: string;
|
|
||||||
onConfigNameChange: (name: string) => void;
|
|
||||||
onSave: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SaveConfigModal = ({
|
|
||||||
opened,
|
|
||||||
onClose,
|
|
||||||
configName,
|
|
||||||
onConfigNameChange,
|
|
||||||
onSave,
|
|
||||||
}: SaveConfigModalProps) => (
|
|
||||||
<Modal opened={opened} onClose={onClose} title="Save Configuration" size="sm">
|
|
||||||
<Stack gap="md">
|
|
||||||
<TextInput
|
|
||||||
label="Configuration Name"
|
|
||||||
placeholder="Enter a name for this configuration"
|
|
||||||
value={configName}
|
|
||||||
onChange={(event) => onConfigNameChange(event.currentTarget.value)}
|
|
||||||
data-autofocus
|
|
||||||
/>
|
|
||||||
<Group justify="flex-end" gap="sm">
|
|
||||||
<Button variant="outline" onClick={onClose}>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
disabled={!configName.trim()}
|
|
||||||
leftSection={<Save size={16} />}
|
|
||||||
onClick={onSave}
|
|
||||||
>
|
|
||||||
Save Configuration
|
|
||||||
</Button>
|
|
||||||
</Group>
|
|
||||||
</Stack>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
|
|
@ -7,15 +7,24 @@ import {
|
||||||
Title,
|
Title,
|
||||||
Group,
|
Group,
|
||||||
Button,
|
Button,
|
||||||
|
Select,
|
||||||
|
Modal,
|
||||||
|
TextInput,
|
||||||
|
Badge,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import {
|
||||||
|
useState,
|
||||||
|
useEffect,
|
||||||
|
useCallback,
|
||||||
|
forwardRef,
|
||||||
|
type ComponentPropsWithoutRef,
|
||||||
|
} from 'react';
|
||||||
|
import { Save, File, Plus } from 'lucide-react';
|
||||||
import { useLaunchConfig } from '@/hooks/useLaunchConfig';
|
import { useLaunchConfig } from '@/hooks/useLaunchConfig';
|
||||||
import { ConfigurationManager } from '@/screens/Launch/ConfigurationManager';
|
|
||||||
import { GeneralTab } from '@/screens/Launch/GeneralTab';
|
import { GeneralTab } from '@/screens/Launch/GeneralTab';
|
||||||
import { AdvancedTab } from '@/screens/Launch/AdvancedTab';
|
import { AdvancedTab } from '@/screens/Launch/AdvancedTab';
|
||||||
import { NetworkTab } from '@/screens/Launch/NetworkTab';
|
import { NetworkTab } from '@/screens/Launch/NetworkTab';
|
||||||
import { ImageGenerationTab } from '@/screens/Launch/ImageGenerationTab';
|
import { ImageGenerationTab } from '@/screens/Launch/ImageGenerationTab';
|
||||||
import { SaveConfigModal } from '@/screens/Launch/SaveConfigModal';
|
|
||||||
import type { ConfigFile } from '@/types';
|
import type { ConfigFile } from '@/types';
|
||||||
|
|
||||||
interface LaunchScreenProps {
|
interface LaunchScreenProps {
|
||||||
|
|
@ -23,6 +32,39 @@ interface LaunchScreenProps {
|
||||||
onLaunchModeChange?: (isImageMode: boolean) => void;
|
onLaunchModeChange?: (isImageMode: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SelectItemProps extends ComponentPropsWithoutRef<'div'> {
|
||||||
|
label: string;
|
||||||
|
extension: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getBadgeColor = (extension: string) => {
|
||||||
|
switch (extension.toLowerCase()) {
|
||||||
|
case '.kcpps':
|
||||||
|
return 'blue';
|
||||||
|
case '.kcppt':
|
||||||
|
return 'green';
|
||||||
|
default:
|
||||||
|
return 'gray';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const SelectItem = forwardRef<HTMLDivElement, SelectItemProps>(
|
||||||
|
({ label, extension, ...others }, ref) => (
|
||||||
|
<div ref={ref} {...others}>
|
||||||
|
<Group justify="space-between" wrap="nowrap">
|
||||||
|
<Text size="sm" truncate>
|
||||||
|
{label}
|
||||||
|
</Text>
|
||||||
|
<Badge size="xs" variant="light" color={getBadgeColor(extension)}>
|
||||||
|
{extension}
|
||||||
|
</Badge>
|
||||||
|
</Group>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
SelectItem.displayName = 'SelectItem';
|
||||||
|
|
||||||
export const LaunchScreen = ({
|
export const LaunchScreen = ({
|
||||||
onLaunch,
|
onLaunch,
|
||||||
onLaunchModeChange,
|
onLaunchModeChange,
|
||||||
|
|
@ -32,7 +74,7 @@ export const LaunchScreen = ({
|
||||||
const [, setInstallDir] = useState<string>('');
|
const [, setInstallDir] = useState<string>('');
|
||||||
const [isLaunching, setIsLaunching] = useState(false);
|
const [isLaunching, setIsLaunching] = useState(false);
|
||||||
const [activeTab, setActiveTab] = useState<string | null>('general');
|
const [activeTab, setActiveTab] = useState<string | null>('general');
|
||||||
const [saveModalOpened, setSaveModalOpened] = useState(false);
|
const [saveAsModalOpened, setSaveAsModalOpened] = useState(false);
|
||||||
const [newConfigName, setNewConfigName] = useState('');
|
const [newConfigName, setNewConfigName] = useState('');
|
||||||
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
|
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
|
||||||
const {
|
const {
|
||||||
|
|
@ -52,13 +94,17 @@ export const LaunchScreen = ({
|
||||||
flashattention,
|
flashattention,
|
||||||
noavx2,
|
noavx2,
|
||||||
failsafe,
|
failsafe,
|
||||||
|
lowvram,
|
||||||
|
quantmatmul,
|
||||||
backend,
|
backend,
|
||||||
|
gpuDevice,
|
||||||
sdmodel,
|
sdmodel,
|
||||||
sdt5xxl,
|
sdt5xxl,
|
||||||
sdclipl,
|
sdclipl,
|
||||||
sdclipg,
|
sdclipg,
|
||||||
sdphotomaker,
|
sdphotomaker,
|
||||||
sdvae,
|
sdvae,
|
||||||
|
sdlora,
|
||||||
parseAndApplyConfigFile,
|
parseAndApplyConfigFile,
|
||||||
loadSavedSettings,
|
loadSavedSettings,
|
||||||
loadConfigFromFile,
|
loadConfigFromFile,
|
||||||
|
|
@ -79,7 +125,10 @@ export const LaunchScreen = ({
|
||||||
handleFlashattentionChange,
|
handleFlashattentionChange,
|
||||||
handleNoavx2Change,
|
handleNoavx2Change,
|
||||||
handleFailsafeChange,
|
handleFailsafeChange,
|
||||||
|
handleLowvramChange,
|
||||||
|
handleQuantmatmulChange,
|
||||||
handleBackendChange,
|
handleBackendChange,
|
||||||
|
handleGpuDeviceChange,
|
||||||
handleSdmodelChange,
|
handleSdmodelChange,
|
||||||
handleSelectSdmodelFile,
|
handleSelectSdmodelFile,
|
||||||
handleSdt5xxlChange,
|
handleSdt5xxlChange,
|
||||||
|
|
@ -92,6 +141,8 @@ export const LaunchScreen = ({
|
||||||
handleSelectSdphotomakerFile,
|
handleSelectSdphotomakerFile,
|
||||||
handleSdvaeChange,
|
handleSdvaeChange,
|
||||||
handleSelectSdvaeFile,
|
handleSelectSdvaeFile,
|
||||||
|
handleSdloraChange,
|
||||||
|
handleSelectSdloraFile,
|
||||||
handleApplyPreset,
|
handleApplyPreset,
|
||||||
} = useLaunchConfig();
|
} = useLaunchConfig();
|
||||||
|
|
||||||
|
|
@ -156,7 +207,7 @@ export const LaunchScreen = ({
|
||||||
setHasUnsavedChanges(true);
|
setHasUnsavedChanges(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePortChangeWithTracking = (port: number) => {
|
const handlePortChangeWithTracking = (port: number | undefined) => {
|
||||||
handlePortChange(port);
|
handlePortChange(port);
|
||||||
setHasUnsavedChanges(true);
|
setHasUnsavedChanges(true);
|
||||||
};
|
};
|
||||||
|
|
@ -186,6 +237,16 @@ export const LaunchScreen = ({
|
||||||
setHasUnsavedChanges(true);
|
setHasUnsavedChanges(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleLowvramChangeWithTracking = (lowvram: boolean) => {
|
||||||
|
handleLowvramChange(lowvram);
|
||||||
|
setHasUnsavedChanges(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleQuantmatmulChangeWithTracking = (quantmatmul: boolean) => {
|
||||||
|
handleQuantmatmulChange(quantmatmul);
|
||||||
|
setHasUnsavedChanges(true);
|
||||||
|
};
|
||||||
|
|
||||||
const handleMultiuserChangeWithTracking = (multiuser: boolean) => {
|
const handleMultiuserChangeWithTracking = (multiuser: boolean) => {
|
||||||
handleMultiuserChange(multiuser);
|
handleMultiuserChange(multiuser);
|
||||||
setHasUnsavedChanges(true);
|
setHasUnsavedChanges(true);
|
||||||
|
|
@ -246,6 +307,11 @@ export const LaunchScreen = ({
|
||||||
setHasUnsavedChanges(true);
|
setHasUnsavedChanges(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSdloraChangeWithTracking = (path: string) => {
|
||||||
|
handleSdloraChange(path);
|
||||||
|
setHasUnsavedChanges(true);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
void loadConfigFiles();
|
void loadConfigFiles();
|
||||||
|
|
||||||
|
|
@ -302,6 +368,10 @@ export const LaunchScreen = ({
|
||||||
if (sdvae.trim()) {
|
if (sdvae.trim()) {
|
||||||
args.push('--sdvae', sdvae);
|
args.push('--sdvae', sdvae);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sdlora.trim()) {
|
||||||
|
args.push('--sdlora', sdlora);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
args.push('--model', modelPath);
|
args.push('--model', modelPath);
|
||||||
}
|
}
|
||||||
|
|
@ -316,8 +386,9 @@ export const LaunchScreen = ({
|
||||||
args.push('--contextsize', contextSize.toString());
|
args.push('--contextsize', contextSize.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (port !== 5001) {
|
const actualPort = port ?? 5001;
|
||||||
args.push('--port', port.toString());
|
if (port !== undefined) {
|
||||||
|
args.push('--port', actualPort.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (host !== 'localhost') {
|
if (host !== 'localhost') {
|
||||||
|
|
@ -353,10 +424,14 @@ export const LaunchScreen = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (backend && backend !== 'cpu') {
|
if (backend && backend !== 'cpu') {
|
||||||
if (backend === 'cuda') {
|
if (backend === 'cuda' || backend === 'rocm') {
|
||||||
args.push('--usecuda');
|
const cudaArgs = ['--usecuda'];
|
||||||
} else if (backend === 'rocm') {
|
|
||||||
args.push('--usecuda');
|
cudaArgs.push(lowvram ? 'lowvram' : 'normal');
|
||||||
|
cudaArgs.push(gpuDevice.toString());
|
||||||
|
cudaArgs.push(quantmatmul ? 'mmq' : 'nommq');
|
||||||
|
|
||||||
|
args.push(...cudaArgs);
|
||||||
} else if (backend === 'vulkan') {
|
} else if (backend === 'vulkan') {
|
||||||
args.push('--usevulkan');
|
args.push('--usevulkan');
|
||||||
} else if (backend === 'clblast') {
|
} else if (backend === 'clblast') {
|
||||||
|
|
@ -394,23 +469,11 @@ export const LaunchScreen = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container size="sm">
|
<Container size="sm">
|
||||||
|
<Stack gap="md">
|
||||||
|
<Card withBorder radius="md" shadow="sm" p="lg">
|
||||||
<Stack gap="lg">
|
<Stack gap="lg">
|
||||||
<Card withBorder radius="md" shadow="sm">
|
|
||||||
<Group justify="space-between" align="center">
|
<Group justify="space-between" align="center">
|
||||||
<div>
|
|
||||||
<Title order={3}>Launch Configuration</Title>
|
<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
|
<Button
|
||||||
radius="md"
|
radius="md"
|
||||||
disabled={(!modelPath && !sdmodel) || isLaunching}
|
disabled={(!modelPath && !sdmodel) || isLaunching}
|
||||||
|
|
@ -418,23 +481,104 @@ export const LaunchScreen = ({
|
||||||
loading={isLaunching}
|
loading={isLaunching}
|
||||||
size="lg"
|
size="lg"
|
||||||
variant="filled"
|
variant="filled"
|
||||||
|
color="blue"
|
||||||
|
style={{
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: '16px',
|
||||||
|
padding: '12px 28px',
|
||||||
|
minWidth: '120px',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: '0.5px',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{isLaunching ? 'Launching...' : 'Launch'}
|
{isLaunching ? 'Launching...' : 'Launch'}
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card withBorder radius="md">
|
<Stack gap="xs">
|
||||||
<ConfigurationManager
|
<Text fw={500} size="sm">
|
||||||
configFiles={configFiles}
|
Configuration File
|
||||||
selectedFile={selectedFile}
|
</Text>
|
||||||
onFileSelection={handleFileSelection}
|
{configFiles.length === 0 ? (
|
||||||
onSaveAsNew={() => setSaveModalOpened(true)}
|
<Text c="dimmed" size="sm">
|
||||||
onUpdateCurrent={() => {}}
|
No configuration files found in the installation directory.
|
||||||
|
</Text>
|
||||||
|
) : (
|
||||||
|
(() => {
|
||||||
|
const selectData = configFiles.map((file) => {
|
||||||
|
const extension = file.name.split('.').pop() || '';
|
||||||
|
const nameWithoutExtension = file.name.replace(
|
||||||
|
`.${extension}`,
|
||||||
|
''
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
value: file.name,
|
||||||
|
label: nameWithoutExtension,
|
||||||
|
extension: `.${extension}`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group gap="xs" align="flex-end">
|
||||||
|
<div style={{ flex: 1 }}>
|
||||||
|
<Select
|
||||||
|
placeholder="Select a configuration file"
|
||||||
|
value={selectedFile}
|
||||||
|
onChange={(value: string | null) =>
|
||||||
|
value && handleFileSelection(value)
|
||||||
|
}
|
||||||
|
data={selectData}
|
||||||
|
leftSection={<File size={16} />}
|
||||||
|
searchable
|
||||||
|
clearable={false}
|
||||||
|
renderOption={({ option }) => {
|
||||||
|
const dataItem = selectData.find(
|
||||||
|
(item) => item.value === option.value
|
||||||
|
);
|
||||||
|
const extension = dataItem?.extension || '';
|
||||||
|
return (
|
||||||
|
<SelectItem
|
||||||
|
label={option.label}
|
||||||
|
extension={extension}
|
||||||
/>
|
/>
|
||||||
</Card>
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
leftSection={<Plus size={14} />}
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedFile(null);
|
||||||
|
setHasUnsavedChanges(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
New
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
leftSection={<Save size={14} />}
|
||||||
|
size="sm"
|
||||||
|
disabled={!hasUnsavedChanges}
|
||||||
|
onClick={() => {
|
||||||
|
if (selectedFile) {
|
||||||
|
setHasUnsavedChanges(false);
|
||||||
|
} else {
|
||||||
|
setSaveAsModalOpened(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
})()
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
|
||||||
<Card withBorder radius="md">
|
|
||||||
<Tabs value={activeTab} onChange={setActiveTab}>
|
<Tabs value={activeTab} onChange={setActiveTab}>
|
||||||
<Tabs.List>
|
<Tabs.List>
|
||||||
<Tabs.Tab value="general">General</Tabs.Tab>
|
<Tabs.Tab value="general">General</Tabs.Tab>
|
||||||
|
|
@ -451,14 +595,18 @@ export const LaunchScreen = ({
|
||||||
autoGpuLayers={autoGpuLayers}
|
autoGpuLayers={autoGpuLayers}
|
||||||
contextSize={contextSize}
|
contextSize={contextSize}
|
||||||
backend={backend}
|
backend={backend}
|
||||||
|
gpuDevice={gpuDevice}
|
||||||
noavx2={noavx2}
|
noavx2={noavx2}
|
||||||
failsafe={failsafe}
|
failsafe={failsafe}
|
||||||
onModelPathChange={handleModelPathChangeWithTracking}
|
onModelPathChange={handleModelPathChangeWithTracking}
|
||||||
onSelectModelFile={handleSelectModelFile}
|
onSelectModelFile={handleSelectModelFile}
|
||||||
onGpuLayersChange={handleGpuLayersChangeWithTracking}
|
onGpuLayersChange={handleGpuLayersChangeWithTracking}
|
||||||
onAutoGpuLayersChange={handleAutoGpuLayersChangeWithTracking}
|
onAutoGpuLayersChange={
|
||||||
|
handleAutoGpuLayersChangeWithTracking
|
||||||
|
}
|
||||||
onContextSizeChange={handleContextSizeChangeWithTracking}
|
onContextSizeChange={handleContextSizeChangeWithTracking}
|
||||||
onBackendChange={handleBackendChangeWithTracking}
|
onBackendChange={handleBackendChangeWithTracking}
|
||||||
|
onGpuDeviceChange={handleGpuDeviceChange}
|
||||||
/>
|
/>
|
||||||
</Tabs.Panel>
|
</Tabs.Panel>
|
||||||
|
|
||||||
|
|
@ -469,6 +617,9 @@ export const LaunchScreen = ({
|
||||||
flashattention={flashattention}
|
flashattention={flashattention}
|
||||||
noavx2={noavx2}
|
noavx2={noavx2}
|
||||||
failsafe={failsafe}
|
failsafe={failsafe}
|
||||||
|
lowvram={lowvram}
|
||||||
|
quantmatmul={quantmatmul}
|
||||||
|
backend={backend}
|
||||||
onAdditionalArgumentsChange={
|
onAdditionalArgumentsChange={
|
||||||
handleAdditionalArgumentsChangeWithTracking
|
handleAdditionalArgumentsChangeWithTracking
|
||||||
}
|
}
|
||||||
|
|
@ -478,6 +629,8 @@ export const LaunchScreen = ({
|
||||||
}
|
}
|
||||||
onNoavx2Change={handleNoavx2ChangeWithTracking}
|
onNoavx2Change={handleNoavx2ChangeWithTracking}
|
||||||
onFailsafeChange={handleFailsafeChangeWithTracking}
|
onFailsafeChange={handleFailsafeChangeWithTracking}
|
||||||
|
onLowvramChange={handleLowvramChangeWithTracking}
|
||||||
|
onQuantmatmulChange={handleQuantmatmulChangeWithTracking}
|
||||||
/>
|
/>
|
||||||
</Tabs.Panel>
|
</Tabs.Panel>
|
||||||
|
|
||||||
|
|
@ -508,6 +661,7 @@ export const LaunchScreen = ({
|
||||||
sdclipg={sdclipg}
|
sdclipg={sdclipg}
|
||||||
sdphotomaker={sdphotomaker}
|
sdphotomaker={sdphotomaker}
|
||||||
sdvae={sdvae}
|
sdvae={sdvae}
|
||||||
|
sdlora={sdlora}
|
||||||
textModelPath={modelPath}
|
textModelPath={modelPath}
|
||||||
onSdmodelChange={handleSdmodelChangeWithTracking}
|
onSdmodelChange={handleSdmodelChangeWithTracking}
|
||||||
onSelectSdmodelFile={handleSelectSdmodelFile}
|
onSelectSdmodelFile={handleSelectSdmodelFile}
|
||||||
|
|
@ -521,23 +675,51 @@ export const LaunchScreen = ({
|
||||||
onSelectSdphotomakerFile={handleSelectSdphotomakerFile}
|
onSelectSdphotomakerFile={handleSelectSdphotomakerFile}
|
||||||
onSdvaeChange={handleSdvaeChangeWithTracking}
|
onSdvaeChange={handleSdvaeChangeWithTracking}
|
||||||
onSelectSdvaeFile={handleSelectSdvaeFile}
|
onSelectSdvaeFile={handleSelectSdvaeFile}
|
||||||
|
onSdloraChange={handleSdloraChangeWithTracking}
|
||||||
|
onSelectSdloraFile={handleSelectSdloraFile}
|
||||||
onApplyPreset={handleApplyPreset}
|
onApplyPreset={handleApplyPreset}
|
||||||
/>
|
/>
|
||||||
</Tabs.Panel>
|
</Tabs.Panel>
|
||||||
</div>
|
</div>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
</Stack>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<SaveConfigModal
|
<Modal
|
||||||
opened={saveModalOpened}
|
opened={saveAsModalOpened}
|
||||||
onClose={() => setSaveModalOpened(false)}
|
onClose={() => setSaveAsModalOpened(false)}
|
||||||
configName={newConfigName}
|
title="Save Configuration As..."
|
||||||
onConfigNameChange={setNewConfigName}
|
size="sm"
|
||||||
onSave={() => {
|
>
|
||||||
setSaveModalOpened(false);
|
<Stack gap="md">
|
||||||
setNewConfigName('');
|
<TextInput
|
||||||
}}
|
label="Configuration Name"
|
||||||
|
placeholder="Enter a name for this configuration"
|
||||||
|
value={newConfigName}
|
||||||
|
onChange={(event) => setNewConfigName(event.currentTarget.value)}
|
||||||
|
data-autofocus
|
||||||
/>
|
/>
|
||||||
|
<Group justify="flex-end" gap="sm">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => setSaveAsModalOpened(false)}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
disabled={!newConfigName.trim()}
|
||||||
|
leftSection={<Save size={16} />}
|
||||||
|
onClick={() => {
|
||||||
|
setSaveAsModalOpened(false);
|
||||||
|
setNewConfigName('');
|
||||||
|
setHasUnsavedChanges(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Save As New
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Modal>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
13
src/types/electron.d.ts
vendored
13
src/types/electron.d.ts
vendored
|
|
@ -72,6 +72,19 @@ export interface KoboldAPI {
|
||||||
detectROCm: () => Promise<{ supported: boolean; devices: string[] }>;
|
detectROCm: () => Promise<{ supported: boolean; devices: string[] }>;
|
||||||
detectHardware: () => Promise<HardwareInfo>;
|
detectHardware: () => Promise<HardwareInfo>;
|
||||||
detectAllCapabilities: () => Promise<HardwareInfo>;
|
detectAllCapabilities: () => Promise<HardwareInfo>;
|
||||||
|
detectBackendSupport: (binaryPath: string) => Promise<{
|
||||||
|
rocm: boolean;
|
||||||
|
vulkan: boolean;
|
||||||
|
clblast: boolean;
|
||||||
|
noavx2: boolean;
|
||||||
|
failsafe: boolean;
|
||||||
|
cuda: boolean;
|
||||||
|
}>;
|
||||||
|
getAvailableBackends: (
|
||||||
|
binaryPath: string,
|
||||||
|
hardwareCapabilities: GPUCapabilities
|
||||||
|
) => Promise<Array<{ value: string; label: string; devices?: string[] }>>;
|
||||||
|
clearBinaryCache: () => Promise<void>;
|
||||||
getCurrentInstallDir: () => Promise<string>;
|
getCurrentInstallDir: () => Promise<string>;
|
||||||
selectInstallDirectory: () => Promise<string | null>;
|
selectInstallDirectory: () => Promise<string | null>;
|
||||||
downloadRelease: (
|
downloadRelease: (
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
export interface CPUCapabilities {
|
export interface CPUCapabilities {
|
||||||
avx: boolean;
|
avx: boolean;
|
||||||
avx2: boolean;
|
avx2: boolean;
|
||||||
cpuInfo: string[];
|
devices: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GPUCapabilities {
|
export interface GPUCapabilities {
|
||||||
|
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
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