mirror of
https://github.com/lone-cloud/gerbil
synced 2026-06-03 19:54:44 -07:00
re-organize new system info cards into a new System tab, hopefully fix app config re-writing race condition
This commit is contained in:
parent
ec2410c0e4
commit
3c2a07c730
11 changed files with 475 additions and 370 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "gerbil",
|
"name": "gerbil",
|
||||||
"productName": "Gerbil",
|
"productName": "Gerbil",
|
||||||
"version": "1.6.0",
|
"version": "1.6.1",
|
||||||
"description": "Run Large Language Models locally",
|
"description": "Run Large Language Models locally",
|
||||||
"main": "out/main/index.js",
|
"main": "out/main/index.js",
|
||||||
"homepage": "./",
|
"homepage": "./",
|
||||||
|
|
|
||||||
|
|
@ -128,7 +128,7 @@ export const StatusBar = ({ maxDataPoints = 60 }: StatusBarProps) => {
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<PerformanceBadge tooltipLabel="System resource manager" iconOnly />
|
<PerformanceBadge tooltipLabel="Resource manager" iconOnly />
|
||||||
)}
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,4 @@
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import {
|
|
||||||
createSoftwareItems,
|
|
||||||
createDriverItems,
|
|
||||||
createHardwareItems,
|
|
||||||
} from '@/utils/systemInfo';
|
|
||||||
import {
|
import {
|
||||||
Text,
|
Text,
|
||||||
Stack,
|
Stack,
|
||||||
|
|
@ -15,11 +10,9 @@ import {
|
||||||
Button,
|
Button,
|
||||||
rem,
|
rem,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { Github, FolderOpen, FileText } from 'lucide-react';
|
import { Github } from 'lucide-react';
|
||||||
import { useLogoClickSounds } from '@/hooks/useLogoClickSounds';
|
import { useLogoClickSounds } from '@/hooks/useLogoClickSounds';
|
||||||
import { PRODUCT_NAME, GITHUB_API } from '@/constants';
|
import { PRODUCT_NAME, GITHUB_API } from '@/constants';
|
||||||
import { InfoCard } from '@/components/InfoCard';
|
|
||||||
import type { HardwareInfo } from '@/types/hardware';
|
|
||||||
import type { SystemVersionInfo } from '@/types/electron';
|
import type { SystemVersionInfo } from '@/types/electron';
|
||||||
|
|
||||||
import icon from '/icon.png';
|
import icon from '/icon.png';
|
||||||
|
|
@ -28,7 +21,6 @@ export const AboutTab = () => {
|
||||||
const [versionInfo, setVersionInfo] = useState<SystemVersionInfo | null>(
|
const [versionInfo, setVersionInfo] = useState<SystemVersionInfo | null>(
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
const [hardwareInfo, setHardwareInfo] = useState<HardwareInfo | null>(null);
|
|
||||||
const { handleLogoClick, getLogoStyles } = useLogoClickSounds();
|
const { handleLogoClick, getLogoStyles } = useLogoClickSounds();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -39,34 +31,7 @@ export const AboutTab = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
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();
|
loadVersionInfo();
|
||||||
loadHardwareInfo();
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (!versionInfo) {
|
if (!versionInfo) {
|
||||||
|
|
@ -77,10 +42,6 @@ export const AboutTab = () => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const softwareItems = createSoftwareItems(versionInfo);
|
|
||||||
const driverItems = hardwareInfo ? createDriverItems(hardwareInfo) : [];
|
|
||||||
const hardwareItems = hardwareInfo ? createHardwareItems(hardwareInfo) : [];
|
|
||||||
|
|
||||||
const actionButtons = [
|
const actionButtons = [
|
||||||
{
|
{
|
||||||
icon: Github,
|
icon: Github,
|
||||||
|
|
@ -88,16 +49,6 @@ export const AboutTab = () => {
|
||||||
onClick: () =>
|
onClick: () =>
|
||||||
window.electronAPI.app.openExternal(GITHUB_API.GERBIL_GITHUB_URL),
|
window.electronAPI.app.openExternal(GITHUB_API.GERBIL_GITHUB_URL),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
icon: FolderOpen,
|
|
||||||
label: 'Show Logs',
|
|
||||||
onClick: () => window.electronAPI.app.showLogsFolder(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: FileText,
|
|
||||||
label: 'View Config',
|
|
||||||
onClick: () => window.electronAPI.app.viewConfigFile(),
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -157,15 +108,18 @@ export const AboutTab = () => {
|
||||||
</Group>
|
</Group>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<InfoCard title="Software" items={softwareItems} />
|
<Card withBorder radius="md" p="md">
|
||||||
|
<Text size="lg" fw={500} mb="md">
|
||||||
<InfoCard title="Drivers" items={driverItems} loading={!hardwareInfo} />
|
About {PRODUCT_NAME}
|
||||||
|
</Text>
|
||||||
<InfoCard
|
<Text size="sm" c="dimmed" mb="md">
|
||||||
title="Hardware"
|
{PRODUCT_NAME} is a user-friendly desktop application that makes it
|
||||||
items={hardwareItems}
|
easy to run large language models locally on your machine. Whether
|
||||||
loading={!hardwareInfo}
|
you're looking to chat with AI models, generate images, or
|
||||||
/>
|
explore different interfaces like SillyTavern and Open WebUI,{' '}
|
||||||
|
{PRODUCT_NAME} provides a streamlined experience for local AI.
|
||||||
|
</Text>
|
||||||
|
</Card>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -11,14 +11,21 @@ import {
|
||||||
import { Sun, Moon, Monitor } from 'lucide-react';
|
import { Sun, Moon, Monitor } from 'lucide-react';
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { usePreferencesStore } from '@/stores/preferences';
|
import { usePreferencesStore } from '@/stores/preferences';
|
||||||
|
import { ZOOM } from '@/constants';
|
||||||
|
import { FrontendInterfaceSelector } from '@/components/settings/FrontendInterfaceSelector';
|
||||||
import {
|
import {
|
||||||
zoomLevelToPercentage,
|
zoomLevelToPercentage,
|
||||||
percentageToZoomLevel,
|
percentageToZoomLevel,
|
||||||
isValidZoomPercentage,
|
isValidZoomPercentage,
|
||||||
} from '@/utils/zoom';
|
} from '@/utils/zoom';
|
||||||
import { ZOOM } from '@/constants';
|
|
||||||
|
|
||||||
export const AppearanceTab = () => {
|
interface AppearanceTabProps {
|
||||||
|
isOnInterfaceScreen?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AppearanceTab = ({
|
||||||
|
isOnInterfaceScreen = false,
|
||||||
|
}: AppearanceTabProps) => {
|
||||||
const {
|
const {
|
||||||
rawColorScheme,
|
rawColorScheme,
|
||||||
resolvedColorScheme,
|
resolvedColorScheme,
|
||||||
|
|
@ -70,6 +77,9 @@ export const AppearanceTab = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack gap="lg" h="100%">
|
<Stack gap="lg" h="100%">
|
||||||
|
<div>
|
||||||
|
<FrontendInterfaceSelector isOnInterfaceScreen={isOnInterfaceScreen} />
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Text fw={500} mb="sm">
|
<Text fw={500} mb="sm">
|
||||||
Theme
|
Theme
|
||||||
|
|
@ -127,7 +137,6 @@ export const AppearanceTab = () => {
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Text fw={500} mb="sm">
|
<Text fw={500} mb="sm">
|
||||||
Zoom Level
|
Zoom Level
|
||||||
|
|
|
||||||
270
src/components/settings/FrontendInterfaceSelector.tsx
Normal file
270
src/components/settings/FrontendInterfaceSelector.tsx
Normal file
|
|
@ -0,0 +1,270 @@
|
||||||
|
import { useState, useEffect, useCallback, useMemo } from 'react';
|
||||||
|
import { Text, Box, Anchor, rem } from '@mantine/core';
|
||||||
|
import { Monitor } from 'lucide-react';
|
||||||
|
import { usePreferencesStore } from '@/stores/preferences';
|
||||||
|
import type { FrontendPreference } from '@/types';
|
||||||
|
import { FRONTENDS } from '@/constants';
|
||||||
|
import { Select } from '@/components/Select';
|
||||||
|
|
||||||
|
interface FrontendRequirement {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FrontendConfig {
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
badges: string[];
|
||||||
|
requirements?: FrontendRequirement[];
|
||||||
|
requirementCheck?: () => Promise<boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FrontendInterfaceSelectorProps {
|
||||||
|
isOnInterfaceScreen?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FrontendInterfaceSelector = ({
|
||||||
|
isOnInterfaceScreen = false,
|
||||||
|
}: FrontendInterfaceSelectorProps) => {
|
||||||
|
const { frontendPreference, setFrontendPreference } = usePreferencesStore();
|
||||||
|
|
||||||
|
const [frontendRequirements, setFrontendRequirements] = useState<
|
||||||
|
Map<string, boolean>
|
||||||
|
>(new Map());
|
||||||
|
|
||||||
|
const frontendConfigs: FrontendConfig[] = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
value: 'koboldcpp',
|
||||||
|
label: 'Built-in',
|
||||||
|
badges: ['Text', 'Image'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'sillytavern',
|
||||||
|
label: FRONTENDS.SILLYTAVERN,
|
||||||
|
badges: ['Text', 'Image'],
|
||||||
|
requirements: [
|
||||||
|
{
|
||||||
|
id: 'nodejs',
|
||||||
|
name: 'Node.js',
|
||||||
|
url: 'https://nodejs.org/',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
requirementCheck: () =>
|
||||||
|
window.electronAPI.dependencies.isNpxAvailable(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'openwebui',
|
||||||
|
label: FRONTENDS.OPENWEBUI,
|
||||||
|
badges: ['Text', 'Image'],
|
||||||
|
requirements: [
|
||||||
|
{
|
||||||
|
id: 'uv',
|
||||||
|
name: 'uv',
|
||||||
|
url: 'https://docs.astral.sh/uv/getting-started/installation/',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
requirementCheck: () => window.electronAPI.dependencies.isUvAvailable(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'comfyui',
|
||||||
|
label: FRONTENDS.COMFYUI,
|
||||||
|
badges: ['Image'],
|
||||||
|
requirements: [
|
||||||
|
{
|
||||||
|
id: 'uv',
|
||||||
|
name: 'uv',
|
||||||
|
url: 'https://docs.astral.sh/uv/getting-started/installation/',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
requirementCheck: () => window.electronAPI.dependencies.isUvAvailable(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const checkAllFrontendRequirements = useCallback(async () => {
|
||||||
|
const requirementResults = new Map<string, boolean>();
|
||||||
|
|
||||||
|
for (const config of frontendConfigs) {
|
||||||
|
if (config.requirementCheck) {
|
||||||
|
const isAvailable = await config.requirementCheck();
|
||||||
|
requirementResults.set(config.value, isAvailable);
|
||||||
|
} else {
|
||||||
|
requirementResults.set(config.value, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFrontendRequirements(requirementResults);
|
||||||
|
|
||||||
|
const currentFrontendConfig = frontendConfigs.find(
|
||||||
|
(config) => config.value === frontendPreference
|
||||||
|
);
|
||||||
|
if (currentFrontendConfig && !requirementResults.get(frontendPreference)) {
|
||||||
|
setFrontendPreference('koboldcpp');
|
||||||
|
}
|
||||||
|
}, [frontendConfigs, frontendPreference, setFrontendPreference]);
|
||||||
|
|
||||||
|
const getSelectedFrontendConfig = () =>
|
||||||
|
frontendConfigs.find((config) => config.value === frontendPreference);
|
||||||
|
|
||||||
|
const getUnmetRequirements = () => {
|
||||||
|
const selectedConfig = getSelectedFrontendConfig();
|
||||||
|
if (!selectedConfig || !selectedConfig.requirements) return [];
|
||||||
|
|
||||||
|
const isAvailable = frontendRequirements.get(selectedConfig.value) ?? true;
|
||||||
|
return isAvailable ? [] : selectedConfig.requirements;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getUnmetRequirementsForFrontend = (frontendValue: string) => {
|
||||||
|
const config = frontendConfigs.find((c) => c.value === frontendValue);
|
||||||
|
if (!config || !config.requirements) return [];
|
||||||
|
|
||||||
|
const isAvailable = frontendRequirements.get(frontendValue) ?? true;
|
||||||
|
return isAvailable ? [] : config.requirements;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isFrontendAvailable = (frontendValue: string) =>
|
||||||
|
frontendRequirements.get(frontendValue) ?? true;
|
||||||
|
|
||||||
|
const handleFrontendPreferenceChange = (value: string | null) => {
|
||||||
|
setFrontendPreference(value as FrontendPreference);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderDisabledFrontendWarnings = () => {
|
||||||
|
const disabledFrontends = frontendConfigs.filter(
|
||||||
|
(config) => !isFrontendAvailable(config.value)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (disabledFrontends.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const requirementGroups = new Map<string, string[]>();
|
||||||
|
|
||||||
|
disabledFrontends.forEach((config) => {
|
||||||
|
const unmetReqs = getUnmetRequirementsForFrontend(config.value);
|
||||||
|
const reqKey = unmetReqs.map((req) => req.id).join(',');
|
||||||
|
if (!requirementGroups.has(reqKey)) {
|
||||||
|
requirementGroups.set(reqKey, []);
|
||||||
|
}
|
||||||
|
requirementGroups.get(reqKey)!.push(config.label);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box mt="sm">
|
||||||
|
{Array.from(requirementGroups.entries()).map(
|
||||||
|
([reqKey, frontendLabels]) => {
|
||||||
|
const firstDisabledFrontend = disabledFrontends.find(
|
||||||
|
(config) =>
|
||||||
|
getUnmetRequirementsForFrontend(config.value)
|
||||||
|
.map((req) => req.id)
|
||||||
|
.join(',') === reqKey
|
||||||
|
);
|
||||||
|
const unmetReqs = firstDisabledFrontend
|
||||||
|
? getUnmetRequirementsForFrontend(firstDisabledFrontend.value)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const frontendText =
|
||||||
|
frontendLabels.length === 1
|
||||||
|
? frontendLabels[0]
|
||||||
|
: frontendLabels.length === 2
|
||||||
|
? `${frontendLabels[0]} and ${frontendLabels[1]}`
|
||||||
|
: `${frontendLabels.slice(0, -1).join(', ')}, and ${frontendLabels[frontendLabels.length - 1]}`;
|
||||||
|
|
||||||
|
const isAre = frontendLabels.length === 1 ? 'is' : 'are';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Text key={reqKey} size="sm" c="orange">
|
||||||
|
{frontendText} {isAre} disabled - requires{' '}
|
||||||
|
{unmetReqs.map((req, index) => (
|
||||||
|
<span key={req.id}>
|
||||||
|
<Anchor
|
||||||
|
href={req.url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
td="underline"
|
||||||
|
>
|
||||||
|
{req.name}
|
||||||
|
</Anchor>
|
||||||
|
{index < unmetReqs.length - 1 ? ', ' : ''}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const initialize = async () => {
|
||||||
|
await checkAllFrontendRequirements();
|
||||||
|
};
|
||||||
|
initialize();
|
||||||
|
|
||||||
|
const handleFocus = () => {
|
||||||
|
checkAllFrontendRequirements();
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('focus', handleFocus);
|
||||||
|
return () => window.removeEventListener('focus', handleFocus);
|
||||||
|
}, [checkAllFrontendRequirements]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (frontendPreference) {
|
||||||
|
checkAllFrontendRequirements();
|
||||||
|
}
|
||||||
|
}, [frontendPreference, checkAllFrontendRequirements]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Text fw={500} mb="sm">
|
||||||
|
Frontend Interface
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
{getUnmetRequirements().length === 0 && (
|
||||||
|
<Text size="sm" c="dimmed" mb="md">
|
||||||
|
Choose which frontend interface to use for interacting with AI models
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{getUnmetRequirements().length > 0 && (
|
||||||
|
<Text size="sm" c="red" mb="md">
|
||||||
|
{getSelectedFrontendConfig()?.label} requires{' '}
|
||||||
|
{getUnmetRequirements().map((req, index) => (
|
||||||
|
<span key={req.id}>
|
||||||
|
<Anchor
|
||||||
|
href={req.url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
c="red"
|
||||||
|
td="underline"
|
||||||
|
>
|
||||||
|
{req.name}
|
||||||
|
</Anchor>
|
||||||
|
{index < getUnmetRequirements().length - 1 ? ', ' : ''}
|
||||||
|
</span>
|
||||||
|
))}{' '}
|
||||||
|
to be installed on your system
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Select
|
||||||
|
value={frontendPreference}
|
||||||
|
onChange={handleFrontendPreferenceChange}
|
||||||
|
disabled={isOnInterfaceScreen}
|
||||||
|
data={frontendConfigs.map((config) => ({
|
||||||
|
value: config.value,
|
||||||
|
label: config.label,
|
||||||
|
disabled: !isFrontendAvailable(config.value),
|
||||||
|
}))}
|
||||||
|
leftSection={<Monitor style={{ width: rem(16), height: rem(16) }} />}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{renderDisabledFrontendWarnings()}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useState, useEffect, useCallback, useMemo } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import {
|
import {
|
||||||
Stack,
|
Stack,
|
||||||
Text,
|
Text,
|
||||||
|
|
@ -6,165 +6,19 @@ import {
|
||||||
TextInput,
|
TextInput,
|
||||||
Button,
|
Button,
|
||||||
rem,
|
rem,
|
||||||
Box,
|
|
||||||
Anchor,
|
|
||||||
Switch,
|
Switch,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { Folder, FolderOpen, Monitor, ExternalLink } from 'lucide-react';
|
import { Folder, FolderOpen, Monitor, ExternalLink } from 'lucide-react';
|
||||||
import type { FrontendPreference } from '@/types';
|
|
||||||
import { usePreferencesStore } from '@/stores/preferences';
|
import { usePreferencesStore } from '@/stores/preferences';
|
||||||
import { FRONTENDS } from '@/constants';
|
|
||||||
import { Select } from '@/components/Select';
|
|
||||||
|
|
||||||
interface FrontendRequirement {
|
export const GeneralTab = () => {
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
url: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface FrontendConfig {
|
|
||||||
value: string;
|
|
||||||
label: string;
|
|
||||||
badges: string[];
|
|
||||||
requirements?: FrontendRequirement[];
|
|
||||||
requirementCheck?: () => Promise<boolean>;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface GeneralTabProps {
|
|
||||||
isOnInterfaceScreen?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const GeneralTab = ({
|
|
||||||
isOnInterfaceScreen = false,
|
|
||||||
}: GeneralTabProps) => {
|
|
||||||
const [installDir, setInstallDir] = useState('');
|
const [installDir, setInstallDir] = useState('');
|
||||||
const {
|
const { systemMonitoringEnabled, setSystemMonitoringEnabled } =
|
||||||
frontendPreference,
|
usePreferencesStore();
|
||||||
setFrontendPreference,
|
|
||||||
systemMonitoringEnabled,
|
|
||||||
setSystemMonitoringEnabled,
|
|
||||||
} = usePreferencesStore();
|
|
||||||
const [frontendRequirements, setFrontendRequirements] = useState<
|
|
||||||
Map<string, boolean>
|
|
||||||
>(new Map());
|
|
||||||
|
|
||||||
const frontendConfigs: FrontendConfig[] = useMemo(
|
|
||||||
() => [
|
|
||||||
{
|
|
||||||
value: 'koboldcpp',
|
|
||||||
label: 'Built-in',
|
|
||||||
badges: ['Text', 'Image'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'sillytavern',
|
|
||||||
label: FRONTENDS.SILLYTAVERN,
|
|
||||||
badges: ['Text', 'Image'],
|
|
||||||
requirements: [
|
|
||||||
{
|
|
||||||
id: 'nodejs',
|
|
||||||
name: 'Node.js',
|
|
||||||
url: 'https://nodejs.org/',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
requirementCheck: () =>
|
|
||||||
window.electronAPI.dependencies.isNpxAvailable(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'openwebui',
|
|
||||||
label: FRONTENDS.OPENWEBUI,
|
|
||||||
badges: ['Text', 'Image'],
|
|
||||||
requirements: [
|
|
||||||
{
|
|
||||||
id: 'uv',
|
|
||||||
name: 'uv',
|
|
||||||
url: 'https://docs.astral.sh/uv/getting-started/installation/',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
requirementCheck: () => window.electronAPI.dependencies.isUvAvailable(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'comfyui',
|
|
||||||
label: FRONTENDS.COMFYUI,
|
|
||||||
badges: ['Image'],
|
|
||||||
requirements: [
|
|
||||||
{
|
|
||||||
id: 'uv',
|
|
||||||
name: 'uv',
|
|
||||||
url: 'https://docs.astral.sh/uv/getting-started/installation/',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
requirementCheck: () => window.electronAPI.dependencies.isUvAvailable(),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const checkAllFrontendRequirements = useCallback(async () => {
|
|
||||||
const requirementResults = new Map<string, boolean>();
|
|
||||||
|
|
||||||
for (const config of frontendConfigs) {
|
|
||||||
if (config.requirementCheck) {
|
|
||||||
const isAvailable = await config.requirementCheck();
|
|
||||||
requirementResults.set(config.value, isAvailable);
|
|
||||||
} else {
|
|
||||||
requirementResults.set(config.value, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setFrontendRequirements(requirementResults);
|
|
||||||
|
|
||||||
const currentFrontendConfig = frontendConfigs.find(
|
|
||||||
(config) => config.value === frontendPreference
|
|
||||||
);
|
|
||||||
if (currentFrontendConfig && !requirementResults.get(frontendPreference)) {
|
|
||||||
setFrontendPreference('koboldcpp');
|
|
||||||
}
|
|
||||||
}, [frontendConfigs, frontendPreference, setFrontendPreference]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const initialize = async () => {
|
loadCurrentInstallDir();
|
||||||
await Promise.all([
|
}, []);
|
||||||
loadCurrentInstallDir(),
|
|
||||||
checkAllFrontendRequirements(),
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
initialize();
|
|
||||||
|
|
||||||
const handleFocus = () => {
|
|
||||||
checkAllFrontendRequirements();
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener('focus', handleFocus);
|
|
||||||
return () => window.removeEventListener('focus', handleFocus);
|
|
||||||
}, [checkAllFrontendRequirements]);
|
|
||||||
|
|
||||||
const getSelectedFrontendConfig = () =>
|
|
||||||
frontendConfigs.find((config) => config.value === frontendPreference);
|
|
||||||
|
|
||||||
const getUnmetRequirements = () => {
|
|
||||||
const selectedConfig = getSelectedFrontendConfig();
|
|
||||||
if (!selectedConfig || !selectedConfig.requirements) return [];
|
|
||||||
|
|
||||||
const isAvailable = frontendRequirements.get(selectedConfig.value) ?? true;
|
|
||||||
return isAvailable ? [] : selectedConfig.requirements;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getUnmetRequirementsForFrontend = (frontendValue: string) => {
|
|
||||||
const config = frontendConfigs.find((c) => c.value === frontendValue);
|
|
||||||
if (!config || !config.requirements) return [];
|
|
||||||
|
|
||||||
const isAvailable = frontendRequirements.get(frontendValue) ?? true;
|
|
||||||
return isAvailable ? [] : config.requirements;
|
|
||||||
};
|
|
||||||
|
|
||||||
const isFrontendAvailable = (frontendValue: string) =>
|
|
||||||
frontendRequirements.get(frontendValue) ?? true;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (frontendPreference) {
|
|
||||||
checkAllFrontendRequirements();
|
|
||||||
}
|
|
||||||
}, [frontendPreference, checkAllFrontendRequirements]);
|
|
||||||
|
|
||||||
const loadCurrentInstallDir = async () => {
|
const loadCurrentInstallDir = async () => {
|
||||||
const currentDir = await window.electronAPI.kobold.getCurrentInstallDir();
|
const currentDir = await window.electronAPI.kobold.getCurrentInstallDir();
|
||||||
|
|
@ -187,10 +41,6 @@ export const GeneralTab = ({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFrontendPreferenceChange = (value: string | null) => {
|
|
||||||
setFrontendPreference(value as FrontendPreference);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack gap="lg" h="100%">
|
<Stack gap="lg" h="100%">
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -232,117 +82,10 @@ export const GeneralTab = ({
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Text fw={500} mb="sm">
|
<Text fw={500} mb="sm">
|
||||||
Frontend Interface
|
Status Bar
|
||||||
</Text>
|
|
||||||
|
|
||||||
{getUnmetRequirements().length === 0 && (
|
|
||||||
<Text size="sm" c="dimmed" mb="md">
|
|
||||||
Choose which frontend interface to use for interacting with AI
|
|
||||||
models
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{getUnmetRequirements().length > 0 && (
|
|
||||||
<Text size="sm" c="red" mb="md">
|
|
||||||
{getSelectedFrontendConfig()?.label} requires{' '}
|
|
||||||
{getUnmetRequirements().map((req, index) => (
|
|
||||||
<span key={req.id}>
|
|
||||||
<Anchor
|
|
||||||
href={req.url}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
c="red"
|
|
||||||
td="underline"
|
|
||||||
>
|
|
||||||
{req.name}
|
|
||||||
</Anchor>
|
|
||||||
{index < getUnmetRequirements().length - 1 ? ', ' : ''}
|
|
||||||
</span>
|
|
||||||
))}{' '}
|
|
||||||
to be installed on your system
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Select
|
|
||||||
value={frontendPreference}
|
|
||||||
onChange={handleFrontendPreferenceChange}
|
|
||||||
disabled={isOnInterfaceScreen}
|
|
||||||
data={frontendConfigs.map((config) => ({
|
|
||||||
value: config.value,
|
|
||||||
label: config.label,
|
|
||||||
disabled: !isFrontendAvailable(config.value),
|
|
||||||
}))}
|
|
||||||
leftSection={<Monitor style={{ width: rem(16), height: rem(16) }} />}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Box mt="sm">
|
|
||||||
{(() => {
|
|
||||||
const disabledFrontends = frontendConfigs.filter(
|
|
||||||
(config) => !isFrontendAvailable(config.value)
|
|
||||||
);
|
|
||||||
|
|
||||||
const requirementGroups = new Map<string, string[]>();
|
|
||||||
|
|
||||||
disabledFrontends.forEach((config) => {
|
|
||||||
const unmetReqs = getUnmetRequirementsForFrontend(config.value);
|
|
||||||
const reqKey = unmetReqs.map((req) => req.id).join(',');
|
|
||||||
if (!requirementGroups.has(reqKey)) {
|
|
||||||
requirementGroups.set(reqKey, []);
|
|
||||||
}
|
|
||||||
requirementGroups.get(reqKey)!.push(config.label);
|
|
||||||
});
|
|
||||||
|
|
||||||
return Array.from(requirementGroups.entries()).map(
|
|
||||||
([reqKey, frontendLabels]) => {
|
|
||||||
const firstDisabledFrontend = disabledFrontends.find(
|
|
||||||
(config) =>
|
|
||||||
getUnmetRequirementsForFrontend(config.value)
|
|
||||||
.map((req) => req.id)
|
|
||||||
.join(',') === reqKey
|
|
||||||
);
|
|
||||||
const unmetReqs = firstDisabledFrontend
|
|
||||||
? getUnmetRequirementsForFrontend(firstDisabledFrontend.value)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
const frontendText =
|
|
||||||
frontendLabels.length === 1
|
|
||||||
? frontendLabels[0]
|
|
||||||
: frontendLabels.length === 2
|
|
||||||
? `${frontendLabels[0]} and ${frontendLabels[1]}`
|
|
||||||
: `${frontendLabels.slice(0, -1).join(', ')}, and ${frontendLabels[frontendLabels.length - 1]}`;
|
|
||||||
|
|
||||||
const isAre = frontendLabels.length === 1 ? 'is' : 'are';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Text key={reqKey} size="sm" c="orange" mb="xs">
|
|
||||||
{frontendText} {isAre} disabled - requires{' '}
|
|
||||||
{unmetReqs.map((req, index) => (
|
|
||||||
<span key={req.id}>
|
|
||||||
<Anchor
|
|
||||||
href={req.url}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
td="underline"
|
|
||||||
>
|
|
||||||
{req.name}
|
|
||||||
</Anchor>
|
|
||||||
{index < unmetReqs.length - 1 ? ', ' : ''}
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</Text>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
})()}
|
|
||||||
</Box>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<Text fw={500} mb="sm">
|
|
||||||
System Performance
|
|
||||||
</Text>
|
</Text>
|
||||||
<Text size="sm" c="dimmed" mb="md">
|
<Text size="sm" c="dimmed" mb="md">
|
||||||
Monitor CPU, memory, and GPU usage in the status bar
|
Control what information is displayed in the status bar
|
||||||
</Text>
|
</Text>
|
||||||
<Switch
|
<Switch
|
||||||
label="Show system metrics"
|
label="Show system metrics"
|
||||||
|
|
@ -352,6 +95,37 @@ export const GeneralTab = ({
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Text fw={500} mb="sm">
|
||||||
|
Troubleshooting
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed" mb="md">
|
||||||
|
Diagnostic tools and configuration access
|
||||||
|
</Text>
|
||||||
|
<Group gap="xs">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="compact-sm"
|
||||||
|
leftSection={
|
||||||
|
<FolderOpen style={{ width: rem(16), height: rem(16) }} />
|
||||||
|
}
|
||||||
|
onClick={() => window.electronAPI.app.showLogsFolder()}
|
||||||
|
>
|
||||||
|
Show Logs
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="compact-sm"
|
||||||
|
leftSection={
|
||||||
|
<Monitor style={{ width: rem(16), height: rem(16) }} />
|
||||||
|
}
|
||||||
|
onClick={() => window.electronAPI.app.viewConfigFile()}
|
||||||
|
>
|
||||||
|
View Config
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</div>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,13 @@ import {
|
||||||
Palette,
|
Palette,
|
||||||
SlidersHorizontal,
|
SlidersHorizontal,
|
||||||
GitBranch,
|
GitBranch,
|
||||||
|
Monitor,
|
||||||
Info,
|
Info,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { GeneralTab } from '@/components/settings/GeneralTab';
|
import { GeneralTab } from '@/components/settings/GeneralTab';
|
||||||
import { VersionsTab } from '@/components/settings/VersionsTab';
|
import { VersionsTab } from '@/components/settings/VersionsTab';
|
||||||
import { AppearanceTab } from '@/components/settings/AppearanceTab';
|
import { AppearanceTab } from '@/components/settings/AppearanceTab';
|
||||||
|
import { SystemTab } from '@/components/settings/SystemTab';
|
||||||
import { AboutTab } from '@/components/settings/AboutTab';
|
import { AboutTab } from '@/components/settings/AboutTab';
|
||||||
import type { Screen } from '@/types';
|
import type { Screen } from '@/types';
|
||||||
import { Modal } from '@/components/Modal';
|
import { Modal } from '@/components/Modal';
|
||||||
|
|
@ -129,6 +131,14 @@ export const SettingsModal = ({
|
||||||
>
|
>
|
||||||
Appearance
|
Appearance
|
||||||
</Tabs.Tab>
|
</Tabs.Tab>
|
||||||
|
<Tabs.Tab
|
||||||
|
value="system"
|
||||||
|
leftSection={
|
||||||
|
<Monitor style={{ width: rem(16), height: rem(16) }} />
|
||||||
|
}
|
||||||
|
>
|
||||||
|
System
|
||||||
|
</Tabs.Tab>
|
||||||
<Tabs.Tab
|
<Tabs.Tab
|
||||||
value="about"
|
value="about"
|
||||||
leftSection={<Info style={{ width: rem(16), height: rem(16) }} />}
|
leftSection={<Info style={{ width: rem(16), height: rem(16) }} />}
|
||||||
|
|
@ -138,7 +148,7 @@ export const SettingsModal = ({
|
||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
|
|
||||||
<Tabs.Panel value="general">
|
<Tabs.Panel value="general">
|
||||||
<GeneralTab isOnInterfaceScreen={isOnInterfaceScreen} />
|
<GeneralTab />
|
||||||
</Tabs.Panel>
|
</Tabs.Panel>
|
||||||
|
|
||||||
{showVersionsTab && (
|
{showVersionsTab && (
|
||||||
|
|
@ -148,7 +158,11 @@ export const SettingsModal = ({
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Tabs.Panel value="appearance">
|
<Tabs.Panel value="appearance">
|
||||||
<AppearanceTab />
|
<AppearanceTab isOnInterfaceScreen={isOnInterfaceScreen} />
|
||||||
|
</Tabs.Panel>
|
||||||
|
|
||||||
|
<Tabs.Panel value="system">
|
||||||
|
<SystemTab />
|
||||||
</Tabs.Panel>
|
</Tabs.Panel>
|
||||||
|
|
||||||
<Tabs.Panel value="about">
|
<Tabs.Panel value="about">
|
||||||
|
|
|
||||||
81
src/components/settings/SystemTab.tsx
Normal file
81
src/components/settings/SystemTab.tsx
Normal file
|
|
@ -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<SystemVersionInfo | null>(
|
||||||
|
null
|
||||||
|
);
|
||||||
|
const [hardwareInfo, setHardwareInfo] = useState<HardwareInfo | null>(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 (
|
||||||
|
<Center h="100%">
|
||||||
|
<Text c="dimmed">Loading system information...</Text>
|
||||||
|
</Center>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const softwareItems = createSoftwareItems(versionInfo);
|
||||||
|
const driverItems = hardwareInfo ? createDriverItems(hardwareInfo) : [];
|
||||||
|
const hardwareItems = hardwareInfo ? createHardwareItems(hardwareInfo) : [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack gap="md">
|
||||||
|
<InfoCard title="Software" items={softwareItems} />
|
||||||
|
|
||||||
|
<InfoCard title="Drivers" items={driverItems} loading={!hardwareInfo} />
|
||||||
|
|
||||||
|
<InfoCard
|
||||||
|
title="Hardware"
|
||||||
|
items={hardwareItems}
|
||||||
|
loading={!hardwareInfo}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -51,10 +51,6 @@ async function saveConfig() {
|
||||||
return success !== null;
|
return success !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveConfigAsync() {
|
|
||||||
saveConfig();
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function initialize() {
|
export async function initialize() {
|
||||||
configPath = getConfigDir();
|
configPath = getConfigDir();
|
||||||
config = await loadConfig();
|
config = await loadConfig();
|
||||||
|
|
@ -85,9 +81,9 @@ function getDefaultInstallDir() {
|
||||||
|
|
||||||
export const getInstallDir = () => config.installDir || getDefaultInstallDir();
|
export const getInstallDir = () => config.installDir || getDefaultInstallDir();
|
||||||
|
|
||||||
export function setInstallDir(dir: string) {
|
export async function setInstallDir(dir: string) {
|
||||||
config.installDir = dir;
|
config.installDir = dir;
|
||||||
saveConfigAsync();
|
await saveConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCurrentKoboldBinary() {
|
export function getCurrentKoboldBinary() {
|
||||||
|
|
@ -95,23 +91,23 @@ export function getCurrentKoboldBinary() {
|
||||||
return path ? path.trim() : path;
|
return path ? path.trim() : path;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setCurrentKoboldBinary(binaryPath: string) {
|
export async function setCurrentKoboldBinary(binaryPath: string) {
|
||||||
config.currentKoboldBinary = binaryPath;
|
config.currentKoboldBinary = binaryPath;
|
||||||
saveConfigAsync();
|
await saveConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getSelectedConfig = () => config.selectedConfig;
|
export const getSelectedConfig = () => config.selectedConfig;
|
||||||
|
|
||||||
export function setSelectedConfig(configName: string) {
|
export async function setSelectedConfig(configName: string) {
|
||||||
config.selectedConfig = configName;
|
config.selectedConfig = configName;
|
||||||
saveConfigAsync();
|
await saveConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getColorScheme = () => config.colorScheme || 'auto';
|
export const getColorScheme = () => config.colorScheme || 'auto';
|
||||||
|
|
||||||
export function setColorScheme(colorScheme: MantineColorScheme) {
|
export async function setColorScheme(colorScheme: MantineColorScheme) {
|
||||||
config.colorScheme = colorScheme;
|
config.colorScheme = colorScheme;
|
||||||
saveConfigAsync();
|
await saveConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getBackgroundColor() {
|
export function getBackgroundColor() {
|
||||||
|
|
@ -128,42 +124,42 @@ export function getBackgroundColor() {
|
||||||
|
|
||||||
export const getWindowBounds = () => config.windowBounds;
|
export const getWindowBounds = () => config.windowBounds;
|
||||||
|
|
||||||
export function setWindowBounds(bounds: WindowBounds) {
|
export async function setWindowBounds(bounds: WindowBounds) {
|
||||||
config.windowBounds = bounds;
|
config.windowBounds = bounds;
|
||||||
saveConfigAsync();
|
await saveConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getFrontendPreference = () => config.frontendPreference;
|
export const getFrontendPreference = () => config.frontendPreference;
|
||||||
|
|
||||||
export function setFrontendPreference(preference: FrontendPreference) {
|
export async function setFrontendPreference(preference: FrontendPreference) {
|
||||||
config.frontendPreference = preference;
|
config.frontendPreference = preference;
|
||||||
saveConfigAsync();
|
await saveConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getHasSeenWelcome = () => config.hasSeenWelcome;
|
export const getHasSeenWelcome = () => config.hasSeenWelcome;
|
||||||
|
|
||||||
export function setHasSeenWelcome(hasSeenWelcome: boolean) {
|
export async function setHasSeenWelcome(hasSeenWelcome: boolean) {
|
||||||
config.hasSeenWelcome = hasSeenWelcome;
|
config.hasSeenWelcome = hasSeenWelcome;
|
||||||
saveConfigAsync();
|
await saveConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getSkipEjectConfirmation = () => config.skipEjectConfirmation;
|
export const getSkipEjectConfirmation = () => config.skipEjectConfirmation;
|
||||||
|
|
||||||
export function setSkipEjectConfirmation(skipEjectConfirmation: boolean) {
|
export async function setSkipEjectConfirmation(skipEjectConfirmation: boolean) {
|
||||||
config.skipEjectConfirmation = skipEjectConfirmation;
|
config.skipEjectConfirmation = skipEjectConfirmation;
|
||||||
saveConfigAsync();
|
await saveConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getDismissedUpdates = () => config.dismissedUpdates || [];
|
export const getDismissedUpdates = () => config.dismissedUpdates || [];
|
||||||
|
|
||||||
export function setDismissedUpdates(dismissedUpdates: DismissedUpdate[]) {
|
export async function setDismissedUpdates(dismissedUpdates: DismissedUpdate[]) {
|
||||||
config.dismissedUpdates = dismissedUpdates;
|
config.dismissedUpdates = dismissedUpdates;
|
||||||
saveConfigAsync();
|
await saveConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getZoomLevel = () => config.zoomLevel;
|
export const getZoomLevel = () => config.zoomLevel;
|
||||||
|
|
||||||
export function setZoomLevel(zoomLevel: number) {
|
export async function setZoomLevel(zoomLevel: number) {
|
||||||
config.zoomLevel = zoomLevel;
|
config.zoomLevel = zoomLevel;
|
||||||
saveConfigAsync();
|
await saveConfig();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 { constants } from 'fs';
|
||||||
|
import { dirname } from 'path';
|
||||||
|
|
||||||
export const pathExists = async (path: string) => {
|
export const pathExists = async (path: string) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -20,8 +21,14 @@ export const readJsonFile = async <T = unknown>(path: string) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const writeJsonFile = async (path: string, data: unknown) => {
|
export const writeJsonFile = async (path: string, data: unknown) => {
|
||||||
|
const dir = dirname(path);
|
||||||
|
|
||||||
|
await ensureDir(dir);
|
||||||
|
|
||||||
const content = JSON.stringify(data, null, 2);
|
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) => {
|
export const ensureDir = async (path: string) => {
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
if (gpuCapabilities.cuda.devices.length > 0) {
|
||||||
items.push({
|
items.push({
|
||||||
label: 'CUDA',
|
label: 'CUDA',
|
||||||
|
|
@ -55,13 +62,13 @@ export const createDriverItems = (hardwareInfo: HardwareInfo) => {
|
||||||
? gpuCapabilities.cuda.version
|
? gpuCapabilities.cuda.version
|
||||||
: 'Available',
|
: '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) {
|
if (gpuCapabilities.rocm.devices.length > 0) {
|
||||||
|
|
@ -71,13 +78,6 @@ export const createDriverItems = (hardwareInfo: HardwareInfo) => {
|
||||||
? gpuCapabilities.rocm.version
|
? gpuCapabilities.rocm.version
|
||||||
: 'Available',
|
: 'Available',
|
||||||
});
|
});
|
||||||
|
|
||||||
if (gpuCapabilities.rocm.driverVersion) {
|
|
||||||
items.push({
|
|
||||||
label: 'AMD',
|
|
||||||
value: gpuCapabilities.rocm.driverVersion,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gpuCapabilities.vulkan.devices.length > 0) {
|
if (gpuCapabilities.vulkan.devices.length > 0) {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue