@@ -232,117 +82,10 @@ export const GeneralTab = ({
- Frontend Interface
-
-
- {getUnmetRequirements().length === 0 && (
-
- Choose which frontend interface to use for interacting with AI
- models
-
- )}
-
- {getUnmetRequirements().length > 0 && (
-
- {getSelectedFrontendConfig()?.label} requires{' '}
- {getUnmetRequirements().map((req, index) => (
-
-
- {req.name}
-
- {index < getUnmetRequirements().length - 1 ? ', ' : ''}
-
- ))}{' '}
- to be installed on your system
-
- )}
-
-
-
-
-
- System Performance
+ Status Bar
- Monitor CPU, memory, and GPU usage in the status bar
+ Control what information is displayed in the status bar
+
+
+
+ Troubleshooting
+
+
+ Diagnostic tools and configuration access
+
+
+
+ }
+ onClick={() => window.electronAPI.app.showLogsFolder()}
+ >
+ Show Logs
+
+
+ }
+ onClick={() => window.electronAPI.app.viewConfigFile()}
+ >
+ View Config
+
+
+
);
};
diff --git a/src/components/settings/SettingsModal.tsx b/src/components/settings/SettingsModal.tsx
index f097778..8ee5735 100644
--- a/src/components/settings/SettingsModal.tsx
+++ b/src/components/settings/SettingsModal.tsx
@@ -5,11 +5,13 @@ import {
Palette,
SlidersHorizontal,
GitBranch,
+ Monitor,
Info,
} from 'lucide-react';
import { GeneralTab } from '@/components/settings/GeneralTab';
import { VersionsTab } from '@/components/settings/VersionsTab';
import { AppearanceTab } from '@/components/settings/AppearanceTab';
+import { SystemTab } from '@/components/settings/SystemTab';
import { AboutTab } from '@/components/settings/AboutTab';
import type { Screen } from '@/types';
import { Modal } from '@/components/Modal';
@@ -129,6 +131,14 @@ export const SettingsModal = ({
>
Appearance
+
+ }
+ >
+ System
+
}
@@ -138,7 +148,7 @@ export const SettingsModal = ({
-
+
{showVersionsTab && (
@@ -148,7 +158,11 @@ export const SettingsModal = ({
)}
-
+
+
+
+
+
diff --git a/src/components/settings/SystemTab.tsx b/src/components/settings/SystemTab.tsx
new file mode 100644
index 0000000..2346059
--- /dev/null
+++ b/src/components/settings/SystemTab.tsx
@@ -0,0 +1,81 @@
+import { useState, useEffect } from 'react';
+import {
+ createSoftwareItems,
+ createDriverItems,
+ createHardwareItems,
+} from '@/utils/systemInfo';
+import { Stack, Text, Center } from '@mantine/core';
+import { InfoCard } from '@/components/InfoCard';
+import type { HardwareInfo } from '@/types/hardware';
+import type { SystemVersionInfo } from '@/types/electron';
+
+export const SystemTab = () => {
+ const [versionInfo, setVersionInfo] = useState(
+ null
+ );
+ const [hardwareInfo, setHardwareInfo] = useState(null);
+
+ useEffect(() => {
+ const loadVersionInfo = async () => {
+ const info = await window.electronAPI.app.getVersionInfo();
+ if (info) {
+ setVersionInfo(info);
+ }
+ };
+
+ const loadHardwareInfo = async () => {
+ try {
+ const [cpu, gpu, gpuCapabilities, gpuMemory, systemMemory] =
+ await Promise.all([
+ window.electronAPI.kobold.detectCPU(),
+ window.electronAPI.kobold.detectGPU(),
+ window.electronAPI.kobold.detectGPUCapabilities(),
+ window.electronAPI.kobold.detectGPUMemory(),
+ window.electronAPI.kobold.detectSystemMemory(),
+ ]);
+
+ setHardwareInfo({
+ cpu,
+ gpu,
+ gpuCapabilities,
+ gpuMemory,
+ systemMemory,
+ });
+ } catch (error) {
+ window.electronAPI.logs.logError(
+ 'Failed to load hardware info',
+ error as Error
+ );
+ }
+ };
+
+ loadVersionInfo();
+ loadHardwareInfo();
+ }, []);
+
+ if (!versionInfo) {
+ return (
+
+ Loading system information...
+
+ );
+ }
+
+ const softwareItems = createSoftwareItems(versionInfo);
+ const driverItems = hardwareInfo ? createDriverItems(hardwareInfo) : [];
+ const hardwareItems = hardwareInfo ? createHardwareItems(hardwareInfo) : [];
+
+ return (
+
+
+
+
+
+
+
+ );
+};
diff --git a/src/main/modules/config.ts b/src/main/modules/config.ts
index 7788c09..ff2fdba 100644
--- a/src/main/modules/config.ts
+++ b/src/main/modules/config.ts
@@ -51,10 +51,6 @@ async function saveConfig() {
return success !== null;
}
-function saveConfigAsync() {
- saveConfig();
-}
-
export async function initialize() {
configPath = getConfigDir();
config = await loadConfig();
@@ -85,9 +81,9 @@ function getDefaultInstallDir() {
export const getInstallDir = () => config.installDir || getDefaultInstallDir();
-export function setInstallDir(dir: string) {
+export async function setInstallDir(dir: string) {
config.installDir = dir;
- saveConfigAsync();
+ await saveConfig();
}
export function getCurrentKoboldBinary() {
@@ -95,23 +91,23 @@ export function getCurrentKoboldBinary() {
return path ? path.trim() : path;
}
-export function setCurrentKoboldBinary(binaryPath: string) {
+export async function setCurrentKoboldBinary(binaryPath: string) {
config.currentKoboldBinary = binaryPath;
- saveConfigAsync();
+ await saveConfig();
}
export const getSelectedConfig = () => config.selectedConfig;
-export function setSelectedConfig(configName: string) {
+export async function setSelectedConfig(configName: string) {
config.selectedConfig = configName;
- saveConfigAsync();
+ await saveConfig();
}
export const getColorScheme = () => config.colorScheme || 'auto';
-export function setColorScheme(colorScheme: MantineColorScheme) {
+export async function setColorScheme(colorScheme: MantineColorScheme) {
config.colorScheme = colorScheme;
- saveConfigAsync();
+ await saveConfig();
}
export function getBackgroundColor() {
@@ -128,42 +124,42 @@ export function getBackgroundColor() {
export const getWindowBounds = () => config.windowBounds;
-export function setWindowBounds(bounds: WindowBounds) {
+export async function setWindowBounds(bounds: WindowBounds) {
config.windowBounds = bounds;
- saveConfigAsync();
+ await saveConfig();
}
export const getFrontendPreference = () => config.frontendPreference;
-export function setFrontendPreference(preference: FrontendPreference) {
+export async function setFrontendPreference(preference: FrontendPreference) {
config.frontendPreference = preference;
- saveConfigAsync();
+ await saveConfig();
}
export const getHasSeenWelcome = () => config.hasSeenWelcome;
-export function setHasSeenWelcome(hasSeenWelcome: boolean) {
+export async function setHasSeenWelcome(hasSeenWelcome: boolean) {
config.hasSeenWelcome = hasSeenWelcome;
- saveConfigAsync();
+ await saveConfig();
}
export const getSkipEjectConfirmation = () => config.skipEjectConfirmation;
-export function setSkipEjectConfirmation(skipEjectConfirmation: boolean) {
+export async function setSkipEjectConfirmation(skipEjectConfirmation: boolean) {
config.skipEjectConfirmation = skipEjectConfirmation;
- saveConfigAsync();
+ await saveConfig();
}
export const getDismissedUpdates = () => config.dismissedUpdates || [];
-export function setDismissedUpdates(dismissedUpdates: DismissedUpdate[]) {
+export async function setDismissedUpdates(dismissedUpdates: DismissedUpdate[]) {
config.dismissedUpdates = dismissedUpdates;
- saveConfigAsync();
+ await saveConfig();
}
export const getZoomLevel = () => config.zoomLevel;
-export function setZoomLevel(zoomLevel: number) {
+export async function setZoomLevel(zoomLevel: number) {
config.zoomLevel = zoomLevel;
- saveConfigAsync();
+ await saveConfig();
}
diff --git a/src/utils/node/fs.ts b/src/utils/node/fs.ts
index a7b44e1..0ddfab0 100644
--- a/src/utils/node/fs.ts
+++ b/src/utils/node/fs.ts
@@ -1,5 +1,6 @@
-import { readFile, writeFile, access, mkdir } from 'fs/promises';
+import { readFile, writeFile, access, mkdir, rename } from 'fs/promises';
import { constants } from 'fs';
+import { dirname } from 'path';
export const pathExists = async (path: string) => {
try {
@@ -20,8 +21,14 @@ export const readJsonFile = async (path: string) => {
};
export const writeJsonFile = async (path: string, data: unknown) => {
+ const dir = dirname(path);
+
+ await ensureDir(dir);
+
const content = JSON.stringify(data, null, 2);
- await writeFile(path, content, 'utf-8');
+ const tempPath = `${path}.tmp`;
+ await writeFile(tempPath, content, 'utf-8');
+ await rename(tempPath, path);
};
export const ensureDir = async (path: string) => {
diff --git a/src/utils/systemInfo.ts b/src/utils/systemInfo.ts
index 5633dd7..7172f70 100644
--- a/src/utils/systemInfo.ts
+++ b/src/utils/systemInfo.ts
@@ -48,6 +48,13 @@ export const createDriverItems = (hardwareInfo: HardwareInfo) => {
];
}
+ if (gpuCapabilities.cuda.driverVersion) {
+ items.push({
+ label: 'NVIDIA',
+ value: gpuCapabilities.cuda.driverVersion,
+ });
+ }
+
if (gpuCapabilities.cuda.devices.length > 0) {
items.push({
label: 'CUDA',
@@ -55,13 +62,13 @@ export const createDriverItems = (hardwareInfo: HardwareInfo) => {
? gpuCapabilities.cuda.version
: 'Available',
});
+ }
- if (gpuCapabilities.cuda.driverVersion) {
- items.push({
- label: 'NVIDIA',
- value: gpuCapabilities.cuda.driverVersion,
- });
- }
+ if (gpuCapabilities.rocm.driverVersion) {
+ items.push({
+ label: 'AMD',
+ value: gpuCapabilities.rocm.driverVersion,
+ });
}
if (gpuCapabilities.rocm.devices.length > 0) {
@@ -71,13 +78,6 @@ export const createDriverItems = (hardwareInfo: HardwareInfo) => {
? gpuCapabilities.rocm.version
: 'Available',
});
-
- if (gpuCapabilities.rocm.driverVersion) {
- items.push({
- label: 'AMD',
- value: gpuCapabilities.rocm.driverVersion,
- });
- }
}
if (gpuCapabilities.vulkan.devices.length > 0) {