mirror of
https://github.com/lone-cloud/gerbil
synced 2026-06-03 19:54:44 -07:00
new startus bar to display system util info
This commit is contained in:
parent
63f388e696
commit
7455a5a270
39 changed files with 756 additions and 317 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "gerbil",
|
"name": "gerbil",
|
||||||
"productName": "Gerbil",
|
"productName": "Gerbil",
|
||||||
"version": "1.1.0",
|
"version": "1.1.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": "./",
|
||||||
|
|
@ -63,8 +63,8 @@
|
||||||
"vite": "^7.1.5"
|
"vite": "^7.1.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mantine/core": "^8.3.0",
|
"@mantine/core": "^8.3.1",
|
||||||
"@mantine/hooks": "^8.3.0",
|
"@mantine/hooks": "^8.3.1",
|
||||||
"axios": "^1.11.0",
|
"axios": "^1.11.0",
|
||||||
"execa": "^9.6.0",
|
"execa": "^9.6.0",
|
||||||
"lucide-react": "^0.543.0",
|
"lucide-react": "^0.543.0",
|
||||||
|
|
|
||||||
30
src/App.tsx
30
src/App.tsx
|
|
@ -5,15 +5,15 @@ import { LaunchScreen } from '@/components/screens/Launch';
|
||||||
import { InterfaceScreen } from '@/components/screens/Interface';
|
import { InterfaceScreen } from '@/components/screens/Interface';
|
||||||
import { WelcomeScreen } from '@/components/screens/Welcome';
|
import { WelcomeScreen } from '@/components/screens/Welcome';
|
||||||
import { UpdateAvailableModal } from '@/components/UpdateAvailableModal';
|
import { UpdateAvailableModal } from '@/components/UpdateAvailableModal';
|
||||||
import { SettingsModal } from '@/components/settings/SettingsModal';
|
|
||||||
import { EjectConfirmModal } from '@/components/EjectConfirmModal';
|
import { EjectConfirmModal } from '@/components/EjectConfirmModal';
|
||||||
import { ScreenTransition } from '@/components/ScreenTransition';
|
import { ScreenTransition } from '@/components/ScreenTransition';
|
||||||
import { TitleBar } from '@/components/TitleBar';
|
import { TitleBar } from '@/components/TitleBar';
|
||||||
|
import { StatusBar } from '@/components/StatusBar';
|
||||||
import { ErrorBoundary } from '@/components/ErrorBoundary';
|
import { ErrorBoundary } from '@/components/ErrorBoundary';
|
||||||
import { useUpdateChecker } from '@/hooks/useUpdateChecker';
|
import { useUpdateChecker } from '@/hooks/useUpdateChecker';
|
||||||
import { useKoboldVersions } from '@/hooks/useKoboldVersions';
|
import { useKoboldVersions } from '@/hooks/useKoboldVersions';
|
||||||
import { safeExecute } from '@/utils/logger';
|
import { safeExecute } from '@/utils/logger';
|
||||||
import { TITLEBAR_HEIGHT } from '@/constants';
|
import { STATUSBAR_HEIGHT, TITLEBAR_HEIGHT } from '@/constants';
|
||||||
import type { DownloadItem } from '@/types/electron';
|
import type { DownloadItem } from '@/types/electron';
|
||||||
import type { InterfaceTab, FrontendPreference, Screen } from '@/types';
|
import type { InterfaceTab, FrontendPreference, Screen } from '@/types';
|
||||||
|
|
||||||
|
|
@ -24,7 +24,6 @@ export const App = () => {
|
||||||
useState<InterfaceTab>('terminal');
|
useState<InterfaceTab>('terminal');
|
||||||
const [frontendPreference, setFrontendPreference] =
|
const [frontendPreference, setFrontendPreference] =
|
||||||
useState<FrontendPreference>('koboldcpp');
|
useState<FrontendPreference>('koboldcpp');
|
||||||
const [settingsModalOpen, setSettingsModalOpen] = useState(false);
|
|
||||||
const [ejectConfirmModalOpen, setEjectConfirmModalOpen] = useState(false);
|
const [ejectConfirmModalOpen, setEjectConfirmModalOpen] = useState(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|
@ -146,6 +145,7 @@ export const App = () => {
|
||||||
return (
|
return (
|
||||||
<AppShell
|
<AppShell
|
||||||
header={{ height: TITLEBAR_HEIGHT }}
|
header={{ height: TITLEBAR_HEIGHT }}
|
||||||
|
footer={{ height: STATUSBAR_HEIGHT }}
|
||||||
padding={currentScreen === 'interface' ? 0 : 'md'}
|
padding={currentScreen === 'interface' ? 0 : 'md'}
|
||||||
>
|
>
|
||||||
<TitleBar
|
<TitleBar
|
||||||
|
|
@ -153,7 +153,6 @@ export const App = () => {
|
||||||
currentTab={activeInterfaceTab}
|
currentTab={activeInterfaceTab}
|
||||||
onTabChange={setActiveInterfaceTab}
|
onTabChange={setActiveInterfaceTab}
|
||||||
onEject={handleEject}
|
onEject={handleEject}
|
||||||
onOpenSettings={() => setSettingsModalOpen(true)}
|
|
||||||
frontendPreference={frontendPreference}
|
frontendPreference={frontendPreference}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
@ -219,22 +218,15 @@ export const App = () => {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</AppShell.Main>
|
</AppShell.Main>
|
||||||
<SettingsModal
|
|
||||||
isOnInterfaceScreen={currentScreen === 'interface'}
|
<AppShell.Footer>
|
||||||
opened={settingsModalOpen}
|
<StatusBar
|
||||||
onClose={async () => {
|
currentScreen={currentScreen}
|
||||||
setSettingsModalOpen(false);
|
frontendPreference={frontendPreference}
|
||||||
const preference = await safeExecute(
|
onFrontendPreferenceChange={setFrontendPreference}
|
||||||
() =>
|
|
||||||
window.electronAPI.config.get(
|
|
||||||
'frontendPreference'
|
|
||||||
) as Promise<FrontendPreference>,
|
|
||||||
'Failed to load frontend preference:'
|
|
||||||
);
|
|
||||||
setFrontendPreference(preference || 'koboldcpp');
|
|
||||||
}}
|
|
||||||
currentScreen={currentScreen || undefined}
|
|
||||||
/>
|
/>
|
||||||
|
</AppShell.Footer>
|
||||||
|
|
||||||
<EjectConfirmModal
|
<EjectConfirmModal
|
||||||
opened={ejectConfirmModalOpen}
|
opened={ejectConfirmModalOpen}
|
||||||
onClose={() => setEjectConfirmModalOpen(false)}
|
onClose={() => setEjectConfirmModalOpen(false)}
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ export const DownloadCard = ({
|
||||||
onUpdate,
|
onUpdate,
|
||||||
}: DownloadCardProps) => {
|
}: DownloadCardProps) => {
|
||||||
const computedColorScheme = useComputedColorScheme('light', {
|
const computedColorScheme = useComputedColorScheme('light', {
|
||||||
getInitialValueInEffect: false,
|
getInitialValueInEffect: true,
|
||||||
});
|
});
|
||||||
const isDark = computedColorScheme === 'dark';
|
const isDark = computedColorScheme === 'dark';
|
||||||
const renderActionButtons = () => {
|
const renderActionButtons = () => {
|
||||||
|
|
|
||||||
147
src/components/StatusBar.tsx
Normal file
147
src/components/StatusBar.tsx
Normal file
|
|
@ -0,0 +1,147 @@
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Flex,
|
||||||
|
ActionIcon,
|
||||||
|
Tooltip,
|
||||||
|
Badge,
|
||||||
|
Group,
|
||||||
|
useMantineTheme,
|
||||||
|
useComputedColorScheme,
|
||||||
|
} from '@mantine/core';
|
||||||
|
import { Settings } from 'lucide-react';
|
||||||
|
import { SettingsModal } from '@/components/settings/SettingsModal';
|
||||||
|
import { safeExecute } from '@/utils/logger';
|
||||||
|
import type { FrontendPreference, Screen } from '@/types';
|
||||||
|
import type { SystemMetrics } from '@/main/modules/monitoring';
|
||||||
|
|
||||||
|
interface StatusBarProps {
|
||||||
|
maxDataPoints?: number;
|
||||||
|
currentScreen: Screen | null;
|
||||||
|
frontendPreference: FrontendPreference;
|
||||||
|
onFrontendPreferenceChange: (preference: FrontendPreference) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StatusBar = ({
|
||||||
|
maxDataPoints = 60,
|
||||||
|
currentScreen,
|
||||||
|
frontendPreference: _frontendPreference,
|
||||||
|
onFrontendPreferenceChange,
|
||||||
|
}: StatusBarProps) => {
|
||||||
|
const [currentMetrics, setCurrentMetrics] = useState<SystemMetrics | null>(
|
||||||
|
null
|
||||||
|
);
|
||||||
|
const [settingsModalOpen, setSettingsModalOpen] = useState(false);
|
||||||
|
const theme = useMantineTheme();
|
||||||
|
const colorScheme = useComputedColorScheme('light', {
|
||||||
|
getInitialValueInEffect: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let isMounted = true;
|
||||||
|
|
||||||
|
const handleMetrics = async (newMetrics: SystemMetrics) => {
|
||||||
|
if (!isMounted) return;
|
||||||
|
|
||||||
|
setCurrentMetrics(newMetrics);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.electronAPI.monitoring.onMetrics(handleMetrics);
|
||||||
|
void window.electronAPI.monitoring.start();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
isMounted = false;
|
||||||
|
window.electronAPI.monitoring.removeMetricsListener();
|
||||||
|
window.electronAPI.monitoring.stop();
|
||||||
|
};
|
||||||
|
}, [maxDataPoints]);
|
||||||
|
|
||||||
|
if (!currentMetrics) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
pr="xs"
|
||||||
|
style={{
|
||||||
|
borderTop: `1px solid ${colorScheme === 'dark' ? theme.colors.dark[4] : theme.colors.gray[3]}`,
|
||||||
|
backgroundColor:
|
||||||
|
colorScheme === 'dark'
|
||||||
|
? 'var(--mantine-color-dark-8)'
|
||||||
|
: 'var(--mantine-color-gray-1)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Flex align="center" justify="space-between">
|
||||||
|
<Group gap="xs" wrap="nowrap">
|
||||||
|
<Tooltip
|
||||||
|
label={`${currentMetrics.cpu.usage.toFixed(1)}%`}
|
||||||
|
position="top"
|
||||||
|
>
|
||||||
|
<Badge size="sm" variant="light">
|
||||||
|
CPU: {currentMetrics.cpu.usage.toFixed(1)}%
|
||||||
|
</Badge>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip
|
||||||
|
label={`${(currentMetrics.memory.used / 1024 ** 3).toFixed(1)} GB / ${(currentMetrics.memory.total / 1024 ** 3).toFixed(1)} GB (${currentMetrics.memory.usage.toFixed(1)}%)`}
|
||||||
|
position="top"
|
||||||
|
>
|
||||||
|
<Badge size="sm" variant="light">
|
||||||
|
RAM: {currentMetrics.memory.usage.toFixed(1)}%
|
||||||
|
</Badge>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
{currentMetrics.gpu?.map((gpu, index) => (
|
||||||
|
<Group key={`gpu-${index}`} gap={4} wrap="nowrap">
|
||||||
|
<Tooltip label={`${gpu.usage.toFixed(1)}%`} position="top">
|
||||||
|
<Badge size="sm" variant="light">
|
||||||
|
GPU: {gpu.usage.toFixed(1)}%
|
||||||
|
</Badge>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip
|
||||||
|
label={`${(gpu.memoryUsed / 1024 ** 2).toFixed(1)} MB / ${(gpu.memoryTotal / 1024 ** 2).toFixed(1)} MB (${gpu.memoryUsage.toFixed(1)}%)`}
|
||||||
|
position="top"
|
||||||
|
>
|
||||||
|
<Badge size="sm" variant="light">
|
||||||
|
VRAM: {gpu.memoryUsage.toFixed(1)}%
|
||||||
|
</Badge>
|
||||||
|
</Tooltip>
|
||||||
|
</Group>
|
||||||
|
))}
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Tooltip label="Settings" position="top">
|
||||||
|
<ActionIcon
|
||||||
|
variant="subtle"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setSettingsModalOpen(true)}
|
||||||
|
aria-label="Open settings"
|
||||||
|
style={{
|
||||||
|
borderRadius: '0.25rem',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Settings size="1rem" />
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<SettingsModal
|
||||||
|
isOnInterfaceScreen={currentScreen === 'interface'}
|
||||||
|
opened={settingsModalOpen}
|
||||||
|
onClose={async () => {
|
||||||
|
setSettingsModalOpen(false);
|
||||||
|
const preference = await safeExecute(
|
||||||
|
() =>
|
||||||
|
window.electronAPI.config.get(
|
||||||
|
'frontendPreference'
|
||||||
|
) as Promise<FrontendPreference>,
|
||||||
|
'Failed to load frontend preference:'
|
||||||
|
);
|
||||||
|
onFrontendPreferenceChange(preference || 'koboldcpp');
|
||||||
|
}}
|
||||||
|
currentScreen={currentScreen || undefined}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -7,14 +7,7 @@ import {
|
||||||
useComputedColorScheme,
|
useComputedColorScheme,
|
||||||
AppShell,
|
AppShell,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import {
|
import { Minus, Square, X, Copy, CircleFadingArrowUp } from 'lucide-react';
|
||||||
Minus,
|
|
||||||
Square,
|
|
||||||
X,
|
|
||||||
Settings,
|
|
||||||
Copy,
|
|
||||||
CircleFadingArrowUp,
|
|
||||||
} from 'lucide-react';
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { soundAssets, playSound, initializeAudio } from '@/utils/sounds';
|
import { soundAssets, playSound, initializeAudio } from '@/utils/sounds';
|
||||||
import { useAppUpdateChecker } from '@/hooks/useAppUpdateChecker';
|
import { useAppUpdateChecker } from '@/hooks/useAppUpdateChecker';
|
||||||
|
|
@ -28,7 +21,6 @@ interface TitleBarProps {
|
||||||
currentTab: InterfaceTab;
|
currentTab: InterfaceTab;
|
||||||
onTabChange: (tab: InterfaceTab) => void;
|
onTabChange: (tab: InterfaceTab) => void;
|
||||||
onEject: () => void;
|
onEject: () => void;
|
||||||
onOpenSettings: () => void;
|
|
||||||
frontendPreference: FrontendPreference;
|
frontendPreference: FrontendPreference;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -37,11 +29,10 @@ export const TitleBar = ({
|
||||||
currentTab,
|
currentTab,
|
||||||
onTabChange,
|
onTabChange,
|
||||||
onEject,
|
onEject,
|
||||||
onOpenSettings,
|
|
||||||
frontendPreference,
|
frontendPreference,
|
||||||
}: TitleBarProps) => {
|
}: TitleBarProps) => {
|
||||||
const computedColorScheme = useComputedColorScheme('light', {
|
const computedColorScheme = useComputedColorScheme('light', {
|
||||||
getInitialValueInEffect: false,
|
getInitialValueInEffect: true,
|
||||||
});
|
});
|
||||||
const { hasUpdate, releaseUrl } = useAppUpdateChecker();
|
const { hasUpdate, releaseUrl } = useAppUpdateChecker();
|
||||||
const { isImageGenerationMode } = useLaunchConfigStore();
|
const { isImageGenerationMode } = useLaunchConfigStore();
|
||||||
|
|
@ -234,30 +225,6 @@ export const TitleBar = ({
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ActionIcon
|
|
||||||
variant="subtle"
|
|
||||||
size={TITLEBAR_HEIGHT}
|
|
||||||
onClick={onOpenSettings}
|
|
||||||
aria-label="Open settings"
|
|
||||||
tabIndex={-1}
|
|
||||||
style={{
|
|
||||||
borderRadius: '0.25rem',
|
|
||||||
margin: 0,
|
|
||||||
outline: 'none',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Settings size="1.25rem" />
|
|
||||||
</ActionIcon>
|
|
||||||
|
|
||||||
<Box
|
|
||||||
style={{
|
|
||||||
width: '0.0625rem',
|
|
||||||
height: '1.25rem',
|
|
||||||
backgroundColor: 'var(--mantine-color-default-border)',
|
|
||||||
margin: '0 0.25rem',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{[
|
{[
|
||||||
{
|
{
|
||||||
icon: <Minus size="1rem" />,
|
icon: <Minus size="1rem" />,
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { useState, useCallback, useEffect, useRef } from 'react';
|
||||||
import { Card, Text, Title, Loader, Stack, Container } from '@mantine/core';
|
import { Card, Text, Title, Loader, Stack, Container } from '@mantine/core';
|
||||||
import { DownloadCard } from '@/components/DownloadCard';
|
import { DownloadCard } from '@/components/DownloadCard';
|
||||||
import { getPlatformDisplayName } from '@/utils/platform';
|
import { getPlatformDisplayName } from '@/utils/platform';
|
||||||
import { formatDownloadSize } from '@/utils/download';
|
import { formatDownloadSize } from '@/utils/format';
|
||||||
import { getAssetDescription } from '@/utils/assets';
|
import { getAssetDescription } from '@/utils/assets';
|
||||||
import { useKoboldVersions } from '@/hooks/useKoboldVersions';
|
import { useKoboldVersions } from '@/hooks/useKoboldVersions';
|
||||||
import { safeExecute } from '@/utils/logger';
|
import { safeExecute } from '@/utils/logger';
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ export const TerminalTab = forwardRef<TerminalTabRef, TerminalTabProps>(
|
||||||
({ onServerReady, frontendPreference = 'koboldcpp' }, ref) => {
|
({ onServerReady, frontendPreference = 'koboldcpp' }, ref) => {
|
||||||
const { host, port, isImageGenerationMode } = useLaunchConfigStore();
|
const { host, port, isImageGenerationMode } = useLaunchConfigStore();
|
||||||
const computedColorScheme = useComputedColorScheme('light', {
|
const computedColorScheme = useComputedColorScheme('light', {
|
||||||
getInitialValueInEffect: false,
|
getInitialValueInEffect: true,
|
||||||
});
|
});
|
||||||
const [terminalContent, setTerminalContent] = useState<string>('');
|
const [terminalContent, setTerminalContent] = useState<string>('');
|
||||||
const [isUserScrolling, setIsUserScrolling] = useState<boolean>(false);
|
const [isUserScrolling, setIsUserScrolling] = useState<boolean>(false);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Text, Group, Badge } from '@mantine/core';
|
import { Text, Group, Badge, Box } from '@mantine/core';
|
||||||
import type { BackendOption } from '@/types';
|
import type { BackendOption } from '@/types';
|
||||||
|
|
||||||
type BackendSelectItemProps = Omit<BackendOption, 'value'>;
|
type BackendSelectItemProps = Omit<BackendOption, 'value'>;
|
||||||
|
|
@ -9,6 +9,7 @@ export const BackendSelectItem = ({
|
||||||
disabled = false,
|
disabled = false,
|
||||||
}: BackendSelectItemProps) => (
|
}: BackendSelectItemProps) => (
|
||||||
<Group justify="space-between" wrap="nowrap">
|
<Group justify="space-between" wrap="nowrap">
|
||||||
|
<Box w={!disabled ? '3rem' : 'auto'}>
|
||||||
<Text size="sm" truncate>
|
<Text size="sm" truncate>
|
||||||
{label}
|
{label}
|
||||||
{disabled && (
|
{disabled && (
|
||||||
|
|
@ -17,6 +18,7 @@ export const BackendSelectItem = ({
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
|
</Box>
|
||||||
{devices && devices.length > 0 && (
|
{devices && devices.length > 0 && (
|
||||||
<Group gap={4}>
|
<Group gap={4}>
|
||||||
{devices.slice(0, 2).map((device, index) => (
|
{devices.slice(0, 2).map((device, index) => (
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { Card, Container, Stack, Tabs, Group, Button } from '@mantine/core';
|
import { Card, Container, Stack, Tabs, Group, Button } from '@mantine/core';
|
||||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||||
import { tryExecute, error, safeExecute } from '@/utils/logger';
|
import { tryExecute, logError, safeExecute } from '@/utils/logger';
|
||||||
import { useLaunchConfig } from '@/hooks/useLaunchConfig';
|
import { useLaunchConfig } from '@/hooks/useLaunchConfig';
|
||||||
import { useLaunchLogic } from '@/hooks/useLaunchLogic';
|
import { useLaunchLogic } from '@/hooks/useLaunchLogic';
|
||||||
import { useWarnings } from '@/hooks/useWarnings';
|
import { useWarnings } from '@/hooks/useWarnings';
|
||||||
|
|
@ -197,7 +197,7 @@ export const LaunchScreen = ({ onLaunch }: LaunchScreenProps) => {
|
||||||
setSelectedFile(fullConfigName);
|
setSelectedFile(fullConfigName);
|
||||||
await window.electronAPI.kobold.setSelectedConfig(fullConfigName);
|
await window.electronAPI.kobold.setSelectedConfig(fullConfigName);
|
||||||
} else {
|
} else {
|
||||||
error(
|
logError(
|
||||||
'Failed to create new configuration',
|
'Failed to create new configuration',
|
||||||
new Error('Save operation failed')
|
new Error('Save operation failed')
|
||||||
);
|
);
|
||||||
|
|
@ -207,7 +207,7 @@ export const LaunchScreen = ({ onLaunch }: LaunchScreenProps) => {
|
||||||
|
|
||||||
const handleSaveConfig = async () => {
|
const handleSaveConfig = async () => {
|
||||||
if (!selectedFile) {
|
if (!selectedFile) {
|
||||||
error(
|
logError(
|
||||||
'No configuration file selected for saving',
|
'No configuration file selected for saving',
|
||||||
new Error('Selected file is null')
|
new Error('Selected file is null')
|
||||||
);
|
);
|
||||||
|
|
@ -221,7 +221,7 @@ export const LaunchScreen = ({ onLaunch }: LaunchScreenProps) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!saveSuccess) {
|
if (!saveSuccess) {
|
||||||
error(
|
logError(
|
||||||
'Failed to save configuration',
|
'Failed to save configuration',
|
||||||
new Error('Save operation failed')
|
new Error('Save operation failed')
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import { ZOOM } from '@/constants';
|
||||||
export const AppearanceTab = () => {
|
export const AppearanceTab = () => {
|
||||||
const { colorScheme, setColorScheme } = useMantineColorScheme();
|
const { colorScheme, setColorScheme } = useMantineColorScheme();
|
||||||
const computedColorScheme = useComputedColorScheme('light', {
|
const computedColorScheme = useComputedColorScheme('light', {
|
||||||
getInitialValueInEffect: false,
|
getInitialValueInEffect: true,
|
||||||
});
|
});
|
||||||
const isDark = computedColorScheme === 'dark';
|
const isDark = computedColorScheme === 'dark';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import {
|
||||||
compareVersions,
|
compareVersions,
|
||||||
} from '@/utils/version';
|
} from '@/utils/version';
|
||||||
import { safeExecute } from '@/utils/logger';
|
import { safeExecute } from '@/utils/logger';
|
||||||
import { formatDownloadSize } from '@/utils/download';
|
import { formatDownloadSize } from '@/utils/format';
|
||||||
|
|
||||||
import { useKoboldVersions } from '@/hooks/useKoboldVersions';
|
import { useKoboldVersions } from '@/hooks/useKoboldVersions';
|
||||||
import type { InstalledVersion, ReleaseWithStatus } from '@/types/electron';
|
import type { InstalledVersion, ReleaseWithStatus } from '@/types/electron';
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ export const CONFIG_FILE_NAME = 'config.json';
|
||||||
|
|
||||||
export const TITLEBAR_HEIGHT = '2.5rem';
|
export const TITLEBAR_HEIGHT = '2.5rem';
|
||||||
|
|
||||||
|
export const STATUSBAR_HEIGHT = '1.5rem';
|
||||||
|
|
||||||
export const MODAL_STYLES_WITH_TITLEBAR = {
|
export const MODAL_STYLES_WITH_TITLEBAR = {
|
||||||
overlay: {
|
overlay: {
|
||||||
top: TITLEBAR_HEIGHT,
|
top: TITLEBAR_HEIGHT,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useState, useCallback, useEffect } from 'react';
|
import { useState, useCallback, useEffect } from 'react';
|
||||||
import { error } from '@/utils/logger';
|
import { logError } from '@/utils/logger';
|
||||||
import { compareVersions } from '@/utils/version';
|
import { compareVersions } from '@/utils/version';
|
||||||
import { GITHUB_API } from '@/constants';
|
import { GITHUB_API } from '@/constants';
|
||||||
|
|
||||||
|
|
@ -50,7 +50,7 @@ export const useAppUpdateChecker = () => {
|
||||||
|
|
||||||
return updateInfo;
|
return updateInfo;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error('Failed to check for app updates:', err as Error);
|
logError('Failed to check for app updates:', err as Error);
|
||||||
return null;
|
return null;
|
||||||
} finally {
|
} finally {
|
||||||
setIsChecking(false);
|
setIsChecking(false);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { error } from '@/utils/logger';
|
import { logError } from '@/utils/logger';
|
||||||
import { getROCmDownload } from '@/utils/rocm';
|
import { getROCmDownload } from '@/utils/rocm';
|
||||||
import { GITHUB_API } from '@/constants';
|
import { GITHUB_API } from '@/constants';
|
||||||
import { filterAssetsByPlatform } from '@/utils/platform';
|
import { filterAssetsByPlatform } from '@/utils/platform';
|
||||||
|
|
@ -48,7 +48,7 @@ const saveToCache = (releases: DownloadItem[]) => {
|
||||||
};
|
};
|
||||||
localStorage.setItem(CACHE_KEY, JSON.stringify(data));
|
localStorage.setItem(CACHE_KEY, JSON.stringify(data));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error('Failed to save releases to cache', err as Error);
|
logError('Failed to save releases to cache', err as Error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -126,7 +126,7 @@ const getLatestReleaseWithDownloadStatus =
|
||||||
availableAssets,
|
availableAssets,
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error('Failed to fetch latest release with status:', err as Error);
|
logError('Failed to fetch latest release with status:', err as Error);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -198,7 +198,7 @@ export const useKoboldVersions = (): UseKoboldVersionsReturn => {
|
||||||
|
|
||||||
setAvailableDownloads(sortDownloadsByType(allDownloads));
|
setAvailableDownloads(sortDownloadsByType(allDownloads));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error('Failed to load remote versions:', err as Error);
|
logError('Failed to load remote versions:', err as Error);
|
||||||
const cached = loadFromCache();
|
const cached = loadFromCache();
|
||||||
if (cached) {
|
if (cached) {
|
||||||
const rocm = await getROCmDownload().catch(() => null);
|
const rocm = await getROCmDownload().catch(() => null);
|
||||||
|
|
@ -236,7 +236,7 @@ export const useKoboldVersions = (): UseKoboldVersionsReturn => {
|
||||||
|
|
||||||
return result.success !== false;
|
return result.success !== false;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error('Failed to download ${item.name}:', err as Error);
|
logError(`Failed to download ${item.name}:`, err as Error);
|
||||||
return false;
|
return false;
|
||||||
} finally {
|
} finally {
|
||||||
setDownloading(null);
|
setDownloading(null);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useState, useCallback } from 'react';
|
import { useState, useCallback } from 'react';
|
||||||
import { error } from '@/utils/logger';
|
import { logError } from '@/utils/logger';
|
||||||
import type { SdConvDirectMode } from '@/types';
|
import type { SdConvDirectMode } from '@/types';
|
||||||
|
|
||||||
interface UseLaunchLogicProps {
|
interface UseLaunchLogicProps {
|
||||||
|
|
@ -283,7 +283,7 @@ export const useLaunchLogic = ({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error('Error launching:', err as Error);
|
logError('Error launching:', err as Error);
|
||||||
} finally {
|
} finally {
|
||||||
setIsLaunching(false);
|
setIsLaunching(false);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useState, useCallback, useEffect } from 'react';
|
import { useState, useCallback, useEffect } from 'react';
|
||||||
import { error } from '@/utils/logger';
|
import { logError } from '@/utils/logger';
|
||||||
import {
|
import {
|
||||||
getDisplayNameFromPath,
|
getDisplayNameFromPath,
|
||||||
compareVersions,
|
compareVersions,
|
||||||
|
|
@ -36,7 +36,7 @@ export const useUpdateChecker = () => {
|
||||||
}
|
}
|
||||||
setDismissedUpdatesLoaded(true);
|
setDismissedUpdatesLoaded(true);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error('Failed to load dismissed updates:', err as Error);
|
logError('Failed to load dismissed updates:', err as Error);
|
||||||
setDismissedUpdatesLoaded(true);
|
setDismissedUpdatesLoaded(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -51,7 +51,7 @@ export const useUpdateChecker = () => {
|
||||||
Array.from(updates)
|
Array.from(updates)
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error('Failed to save dismissed updates:', err as Error);
|
logError('Failed to save dismissed updates:', err as Error);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
@ -106,7 +106,7 @@ export const useUpdateChecker = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error('Failed to check for updates:', err as Error);
|
logError('Failed to check for updates:', err as Error);
|
||||||
} finally {
|
} finally {
|
||||||
setIsChecking(false);
|
setIsChecking(false);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,11 @@ import {
|
||||||
detectBackendSupport,
|
detectBackendSupport,
|
||||||
getAvailableBackends,
|
getAvailableBackends,
|
||||||
} from '@/main/modules/binary';
|
} from '@/main/modules/binary';
|
||||||
|
import {
|
||||||
|
setMainWindow,
|
||||||
|
startMonitoring,
|
||||||
|
stopMonitoring,
|
||||||
|
} from '@/main/modules/monitoring';
|
||||||
import type { FrontendPreference } from '@/types';
|
import type { FrontendPreference } from '@/types';
|
||||||
|
|
||||||
async function launchKoboldCppWithCustomFrontends(args: string[] = []) {
|
async function launchKoboldCppWithCustomFrontends(args: string[] = []) {
|
||||||
|
|
@ -229,4 +234,16 @@ export function setupIPCHandlers() {
|
||||||
);
|
);
|
||||||
|
|
||||||
ipcMain.handle('openwebui:isUvAvailable', () => isUvAvailable());
|
ipcMain.handle('openwebui:isUvAvailable', () => isUvAvailable());
|
||||||
|
|
||||||
|
ipcMain.handle('monitoring:start', () => {
|
||||||
|
const mainWindow = getMainWindow();
|
||||||
|
if (mainWindow) {
|
||||||
|
setMainWindow(mainWindow);
|
||||||
|
startMonitoring();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('monitoring:stop', () => {
|
||||||
|
stopMonitoring();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
/* eslint-disable no-comments/disallowComments */
|
/* eslint-disable no-comments/disallowComments */
|
||||||
import si from 'systeminformation';
|
import si from 'systeminformation';
|
||||||
import { shortenDeviceName } from '@/utils/hardware';
|
|
||||||
import { logError } from '@/main/modules/logging';
|
import { logError } from '@/main/modules/logging';
|
||||||
import { terminateProcess } from '@/utils/process';
|
import { terminateProcess } from '@/utils/process';
|
||||||
import type {
|
import type {
|
||||||
|
|
@ -8,8 +7,10 @@ import type {
|
||||||
GPUCapabilities,
|
GPUCapabilities,
|
||||||
BasicGPUInfo,
|
BasicGPUInfo,
|
||||||
GPUMemoryInfo,
|
GPUMemoryInfo,
|
||||||
|
HardwareDetectionResult,
|
||||||
} from '@/types/hardware';
|
} from '@/types/hardware';
|
||||||
import { spawn } from 'child_process';
|
import { spawn } from 'child_process';
|
||||||
|
import { formatDeviceName } from '@/utils/format';
|
||||||
|
|
||||||
let cpuCapabilitiesCache: CPUCapabilities | null = null;
|
let cpuCapabilitiesCache: CPUCapabilities | null = null;
|
||||||
let basicGPUInfoCache: BasicGPUInfo | null = null;
|
let basicGPUInfoCache: BasicGPUInfo | null = null;
|
||||||
|
|
@ -26,7 +27,7 @@ export async function detectCPU() {
|
||||||
|
|
||||||
const devices: string[] = [];
|
const devices: string[] = [];
|
||||||
if (cpu.brand) {
|
if (cpu.brand) {
|
||||||
devices.push(shortenDeviceName(cpu.brand));
|
devices.push(formatDeviceName(cpu.brand));
|
||||||
}
|
}
|
||||||
|
|
||||||
const avx = flags.includes('avx') || flags.includes('AVX');
|
const avx = flags.includes('avx') || flags.includes('AVX');
|
||||||
|
|
@ -65,7 +66,7 @@ export async function detectGPU() {
|
||||||
|
|
||||||
for (const controller of graphics.controllers) {
|
for (const controller of graphics.controllers) {
|
||||||
if (controller.model) {
|
if (controller.model) {
|
||||||
gpuInfo.push(shortenDeviceName(controller.model));
|
gpuInfo.push(formatDeviceName(controller.model));
|
||||||
}
|
}
|
||||||
|
|
||||||
const vendor = controller.vendor?.toLowerCase() || '';
|
const vendor = controller.vendor?.toLowerCase() || '';
|
||||||
|
|
@ -142,10 +143,7 @@ async function detectCUDA() {
|
||||||
output += data.toString();
|
output += data.toString();
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Promise<{
|
return new Promise<HardwareDetectionResult>((resolve) => {
|
||||||
supported: boolean;
|
|
||||||
devices: string[];
|
|
||||||
}>((resolve) => {
|
|
||||||
nvidia.on('close', (code) => {
|
nvidia.on('close', (code) => {
|
||||||
if (code === 0 && output.trim()) {
|
if (code === 0 && output.trim()) {
|
||||||
const devices = output
|
const devices = output
|
||||||
|
|
@ -154,7 +152,7 @@ async function detectCUDA() {
|
||||||
.map((line) => {
|
.map((line) => {
|
||||||
const parts = line.split(',');
|
const parts = line.split(',');
|
||||||
const rawName = parts[0]?.trim() || 'Unknown NVIDIA GPU';
|
const rawName = parts[0]?.trim() || 'Unknown NVIDIA GPU';
|
||||||
return shortenDeviceName(rawName);
|
return formatDeviceName(rawName);
|
||||||
})
|
})
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
|
|
||||||
|
|
@ -206,10 +204,7 @@ export async function detectROCm() {
|
||||||
output += data.toString();
|
output += data.toString();
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Promise<{
|
return new Promise<HardwareDetectionResult>((resolve) => {
|
||||||
supported: boolean;
|
|
||||||
devices: string[];
|
|
||||||
}>((resolve) => {
|
|
||||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
rocminfo.on('close', (code) => {
|
rocminfo.on('close', (code) => {
|
||||||
if (code === 0 && output.trim()) {
|
if (code === 0 && output.trim()) {
|
||||||
|
|
@ -225,9 +220,9 @@ export async function detectROCm() {
|
||||||
if (
|
if (
|
||||||
name &&
|
name &&
|
||||||
!name.toLowerCase().includes('cpu') &&
|
!name.toLowerCase().includes('cpu') &&
|
||||||
!devices.includes(shortenDeviceName(name))
|
!devices.includes(formatDeviceName(name))
|
||||||
) {
|
) {
|
||||||
devices.push(shortenDeviceName(name));
|
devices.push(formatDeviceName(name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -262,7 +257,7 @@ export async function detectROCm() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (deviceType !== 'CPU') {
|
if (deviceType !== 'CPU') {
|
||||||
devices.push(shortenDeviceName(name));
|
devices.push(formatDeviceName(name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -301,10 +296,7 @@ async function detectVulkan() {
|
||||||
output += data.toString();
|
output += data.toString();
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Promise<{
|
return new Promise<HardwareDetectionResult>((resolve) => {
|
||||||
supported: boolean;
|
|
||||||
devices: string[];
|
|
||||||
}>((resolve) => {
|
|
||||||
vulkaninfo.on('close', (code) => {
|
vulkaninfo.on('close', (code) => {
|
||||||
if (code === 0 && output.trim()) {
|
if (code === 0 && output.trim()) {
|
||||||
const devices: string[] = [];
|
const devices: string[] = [];
|
||||||
|
|
@ -316,7 +308,7 @@ async function detectVulkan() {
|
||||||
if (parts.length >= 2) {
|
if (parts.length >= 2) {
|
||||||
const name = parts[1]?.trim();
|
const name = parts[1]?.trim();
|
||||||
if (name) {
|
if (name) {
|
||||||
devices.push(shortenDeviceName(name));
|
devices.push(formatDeviceName(name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -363,7 +355,7 @@ function parseClInfoOutput(output: string) {
|
||||||
const deviceName = findDeviceNameInClInfo(lines, i);
|
const deviceName = findDeviceNameInClInfo(lines, i);
|
||||||
|
|
||||||
if (deviceName && currentPlatform) {
|
if (deviceName && currentPlatform) {
|
||||||
const deviceLabel = `${shortenDeviceName(deviceName)} (${currentPlatform})`;
|
const deviceLabel = `${formatDeviceName(deviceName)} (${currentPlatform})`;
|
||||||
devices.push(deviceLabel);
|
devices.push(deviceLabel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -407,10 +399,7 @@ async function detectCLBlast() {
|
||||||
output += data.toString();
|
output += data.toString();
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Promise<{
|
return new Promise<HardwareDetectionResult>((resolve) => {
|
||||||
supported: boolean;
|
|
||||||
devices: string[];
|
|
||||||
}>((resolve) => {
|
|
||||||
clinfo.on('close', (code) => {
|
clinfo.on('close', (code) => {
|
||||||
if (code === 0 && output.trim()) {
|
if (code === 0 && output.trim()) {
|
||||||
const devices = parseClInfoOutput(output);
|
const devices = parseClInfoOutput(output);
|
||||||
|
|
@ -456,7 +445,7 @@ export async function detectGPUMemory() {
|
||||||
}
|
}
|
||||||
|
|
||||||
memoryInfo.push({
|
memoryInfo.push({
|
||||||
deviceName: shortenDeviceName(controller.model),
|
deviceName: formatDeviceName(controller.model),
|
||||||
totalMemoryMB: vram,
|
totalMemoryMB: vram,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
299
src/main/modules/monitoring.ts
Normal file
299
src/main/modules/monitoring.ts
Normal file
|
|
@ -0,0 +1,299 @@
|
||||||
|
import si from 'systeminformation';
|
||||||
|
import { BrowserWindow } from 'electron';
|
||||||
|
import { logError } from '@/main/modules/logging';
|
||||||
|
import { readFile, readdir } from 'fs/promises';
|
||||||
|
import { join } from 'path';
|
||||||
|
import { spawn } from 'child_process';
|
||||||
|
import { platform } from 'process';
|
||||||
|
|
||||||
|
export interface SystemMetrics {
|
||||||
|
timestamp: number;
|
||||||
|
cpu: {
|
||||||
|
usage: number;
|
||||||
|
};
|
||||||
|
memory: {
|
||||||
|
used: number;
|
||||||
|
total: number;
|
||||||
|
usage: number;
|
||||||
|
};
|
||||||
|
gpu?: {
|
||||||
|
name: string;
|
||||||
|
usage: number;
|
||||||
|
memoryUsed: number;
|
||||||
|
memoryTotal: number;
|
||||||
|
memoryUsage: number;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
let interval: ReturnType<typeof setInterval> | null = null;
|
||||||
|
let isRunning = false;
|
||||||
|
let updateFrequency = 1000;
|
||||||
|
let mainWindow: BrowserWindow | null = null;
|
||||||
|
|
||||||
|
export function setMainWindow(window: BrowserWindow) {
|
||||||
|
mainWindow = window;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function startMonitoring() {
|
||||||
|
if (isRunning) return;
|
||||||
|
|
||||||
|
isRunning = true;
|
||||||
|
collectAndSendMetrics();
|
||||||
|
interval = setInterval(() => {
|
||||||
|
collectAndSendMetrics();
|
||||||
|
}, updateFrequency);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stopMonitoring() {
|
||||||
|
if (interval) {
|
||||||
|
clearInterval(interval);
|
||||||
|
interval = null;
|
||||||
|
}
|
||||||
|
isRunning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function collectAndSendMetrics() {
|
||||||
|
try {
|
||||||
|
const metrics = await collectMetrics();
|
||||||
|
|
||||||
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||||
|
mainWindow.webContents.send('system-metrics', metrics);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logError('Failed to collect system metrics:', error as Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getWindowsGPUData() {
|
||||||
|
return new Promise<
|
||||||
|
{
|
||||||
|
deviceName: string;
|
||||||
|
usage: number;
|
||||||
|
memoryUsed: number;
|
||||||
|
memoryTotal: number;
|
||||||
|
}[]
|
||||||
|
>((resolve) => {
|
||||||
|
const gpus: {
|
||||||
|
deviceName: string;
|
||||||
|
usage: number;
|
||||||
|
memoryUsed: number;
|
||||||
|
memoryTotal: number;
|
||||||
|
}[] = [];
|
||||||
|
|
||||||
|
const powershellScript = `
|
||||||
|
# Get GPU information using WMI and Performance Counters
|
||||||
|
$gpuInfo = Get-WmiObject -Class Win32_VideoController | Where-Object {$_.Name -notlike "*Microsoft*" -and $_.Name -notlike "*Remote*"}
|
||||||
|
|
||||||
|
foreach ($gpu in $gpuInfo) {
|
||||||
|
$name = $gpu.Name
|
||||||
|
$vramTotal = if ($gpu.AdapterRAM) { $gpu.AdapterRAM } else { 0 }
|
||||||
|
|
||||||
|
# Try to get GPU usage from performance counters
|
||||||
|
$usage = 0
|
||||||
|
try {
|
||||||
|
$counter = Get-Counter "\\GPU Engine(*engtype_3D)\\Utilization Percentage" -ErrorAction SilentlyContinue
|
||||||
|
if ($counter) {
|
||||||
|
$usage = ($counter.CounterSamples | Measure-Object -Property CookedValue -Maximum).Maximum
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
# Try alternative performance counter for GPU usage
|
||||||
|
if ($usage -eq 0) {
|
||||||
|
try {
|
||||||
|
$counter = Get-Counter "\\GPU Process Memory(*)\\Dedicated Usage" -ErrorAction SilentlyContinue
|
||||||
|
if ($counter) {
|
||||||
|
$memUsed = ($counter.CounterSamples | Measure-Object -Property CookedValue -Sum).Sum
|
||||||
|
$usage = if ($vramTotal -gt 0) { [math]::Round(($memUsed / $vramTotal) * 100, 2) } else { 0 }
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Output as JSON
|
||||||
|
@{
|
||||||
|
Name = $name
|
||||||
|
Usage = $usage
|
||||||
|
MemoryUsed = 0
|
||||||
|
MemoryTotal = $vramTotal
|
||||||
|
} | ConvertTo-Json -Compress
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const powershell = spawn(
|
||||||
|
'powershell.exe',
|
||||||
|
[
|
||||||
|
'-NoProfile',
|
||||||
|
'-ExecutionPolicy',
|
||||||
|
'Bypass',
|
||||||
|
'-Command',
|
||||||
|
powershellScript,
|
||||||
|
],
|
||||||
|
{ timeout: 5000 }
|
||||||
|
);
|
||||||
|
|
||||||
|
let output = '';
|
||||||
|
powershell.stdout.on('data', (data) => {
|
||||||
|
output += data.toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
powershell.on('close', (code) => {
|
||||||
|
if (code === 0 && output.trim()) {
|
||||||
|
try {
|
||||||
|
const lines = output
|
||||||
|
.trim()
|
||||||
|
.split('\n')
|
||||||
|
.filter((line) => line.trim());
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
try {
|
||||||
|
const gpuData = JSON.parse(line);
|
||||||
|
if (gpuData.Name) {
|
||||||
|
gpus.push({
|
||||||
|
deviceName: 'GPU',
|
||||||
|
usage: parseFloat(gpuData.Usage) || 0,
|
||||||
|
memoryUsed: parseInt(gpuData.MemoryUsed) || 0,
|
||||||
|
memoryTotal: parseInt(gpuData.MemoryTotal) || 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logError('Failed to parse Windows GPU data:', error as Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(gpus);
|
||||||
|
});
|
||||||
|
|
||||||
|
powershell.on('error', () => {
|
||||||
|
resolve(gpus);
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
powershell.kill();
|
||||||
|
resolve(gpus);
|
||||||
|
}, 5000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getLinuxGPUData() {
|
||||||
|
try {
|
||||||
|
return getLinuxDrmMetrics();
|
||||||
|
} catch (error) {
|
||||||
|
logError('Failed to get Linux GPU data:', error as Error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getLinuxDrmMetrics() {
|
||||||
|
try {
|
||||||
|
const drmPath = '/sys/class/drm';
|
||||||
|
const entries = await readdir(drmPath);
|
||||||
|
const cardEntries = entries.filter(
|
||||||
|
(entry) => entry.startsWith('card') && !entry.includes('-')
|
||||||
|
);
|
||||||
|
|
||||||
|
const gpus = [];
|
||||||
|
for (const card of cardEntries) {
|
||||||
|
const devicePath = join(drmPath, card, 'device');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const usageData = await readFile(
|
||||||
|
`${devicePath}/gpu_busy_percent`,
|
||||||
|
'utf8'
|
||||||
|
);
|
||||||
|
const memUsedData = await readFile(
|
||||||
|
`${devicePath}/mem_info_vram_used`,
|
||||||
|
'utf8'
|
||||||
|
);
|
||||||
|
const memTotalData = await readFile(
|
||||||
|
`${devicePath}/mem_info_vram_total`,
|
||||||
|
'utf8'
|
||||||
|
);
|
||||||
|
|
||||||
|
const usage = parseInt(usageData.trim(), 10) || 0;
|
||||||
|
const memoryUsed = parseInt(memUsedData.trim(), 10) || 0;
|
||||||
|
const memoryTotal = parseInt(memTotalData.trim(), 10) || 0;
|
||||||
|
|
||||||
|
gpus.push({
|
||||||
|
deviceName: 'GPU',
|
||||||
|
usage,
|
||||||
|
memoryUsed,
|
||||||
|
memoryTotal,
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return gpus;
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getGPUData() {
|
||||||
|
if (platform === 'win32') {
|
||||||
|
return getWindowsGPUData();
|
||||||
|
} else if (platform === 'linux') {
|
||||||
|
return getLinuxGPUData();
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function collectMetrics(): Promise<SystemMetrics> {
|
||||||
|
const timestamp = Date.now();
|
||||||
|
|
||||||
|
const [cpuData, memData, gpuData] = await Promise.allSettled([
|
||||||
|
si.currentLoad(),
|
||||||
|
si.mem(),
|
||||||
|
getGPUData(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const cpu = {
|
||||||
|
usage: cpuData.status === 'fulfilled' ? cpuData.value.currentLoad : 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const memory = {
|
||||||
|
used:
|
||||||
|
memData.status === 'fulfilled'
|
||||||
|
? memData.value.active || memData.value.used
|
||||||
|
: 0,
|
||||||
|
total: memData.status === 'fulfilled' ? memData.value.total : 0,
|
||||||
|
usage:
|
||||||
|
memData.status === 'fulfilled'
|
||||||
|
? ((memData.value.active || memData.value.used) / memData.value.total) *
|
||||||
|
100
|
||||||
|
: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let gpu: SystemMetrics['gpu'];
|
||||||
|
if (gpuData.status === 'fulfilled' && gpuData.value.length > 0) {
|
||||||
|
gpu = gpuData.value.map((gpuInfo: {
|
||||||
|
deviceName: string;
|
||||||
|
usage: number;
|
||||||
|
memoryUsed: number;
|
||||||
|
memoryTotal: number;
|
||||||
|
}) => ({
|
||||||
|
name: gpuInfo.deviceName,
|
||||||
|
usage: gpuInfo.usage,
|
||||||
|
memoryUsed: gpuInfo.memoryUsed,
|
||||||
|
memoryTotal: gpuInfo.memoryTotal,
|
||||||
|
memoryUsage:
|
||||||
|
gpuInfo.memoryTotal > 0
|
||||||
|
? (gpuInfo.memoryUsed / gpuInfo.memoryTotal) * 100
|
||||||
|
: 0,
|
||||||
|
}));
|
||||||
|
} else if (gpuData.status === 'rejected') {
|
||||||
|
logError('GPU detection failed', gpuData.reason as Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
timestamp,
|
||||||
|
cpu,
|
||||||
|
memory,
|
||||||
|
gpu,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -18,6 +18,8 @@ export function createMainWindow() {
|
||||||
: join(__dirname, '../../assets/icon.png');
|
: join(__dirname, '../../assets/icon.png');
|
||||||
|
|
||||||
mainWindow = new BrowserWindow({
|
mainWindow = new BrowserWindow({
|
||||||
|
minWidth: 600,
|
||||||
|
minHeight: 600,
|
||||||
width: windowWidth,
|
width: windowWidth,
|
||||||
height: windowHeight,
|
height: windowHeight,
|
||||||
x: Math.floor((size.width - windowWidth) / 2),
|
x: Math.floor((size.width - windowWidth) / 2),
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import type {
|
||||||
LogsAPI,
|
LogsAPI,
|
||||||
SillyTavernAPI,
|
SillyTavernAPI,
|
||||||
OpenWebUIAPI,
|
OpenWebUIAPI,
|
||||||
|
MonitoringAPI,
|
||||||
} from '@/types/electron';
|
} from '@/types/electron';
|
||||||
|
|
||||||
const koboldAPI: KoboldAPI = {
|
const koboldAPI: KoboldAPI = {
|
||||||
|
|
@ -107,6 +108,17 @@ const openwebUIAPI: OpenWebUIAPI = {
|
||||||
isUvAvailable: () => ipcRenderer.invoke('openwebui:isUvAvailable'),
|
isUvAvailable: () => ipcRenderer.invoke('openwebui:isUvAvailable'),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const monitoringAPI: MonitoringAPI = {
|
||||||
|
start: () => ipcRenderer.invoke('monitoring:start'),
|
||||||
|
stop: () => ipcRenderer.invoke('monitoring:stop'),
|
||||||
|
onMetrics: (callback) => {
|
||||||
|
ipcRenderer.on('system-metrics', (_, metrics) => callback(metrics));
|
||||||
|
},
|
||||||
|
removeMetricsListener: () => {
|
||||||
|
ipcRenderer.removeAllListeners('system-metrics');
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld('electronAPI', {
|
contextBridge.exposeInMainWorld('electronAPI', {
|
||||||
kobold: koboldAPI,
|
kobold: koboldAPI,
|
||||||
app: appAPI,
|
app: appAPI,
|
||||||
|
|
@ -114,4 +126,5 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
||||||
logs: logsAPI,
|
logs: logsAPI,
|
||||||
sillytavern: sillyTavernAPI,
|
sillytavern: sillyTavernAPI,
|
||||||
openwebui: openwebUIAPI,
|
openwebui: openwebUIAPI,
|
||||||
|
monitoring: monitoringAPI,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -375,7 +375,7 @@ export const useLaunchConfigStore = create<LaunchConfigState>((set, get) => ({
|
||||||
loadConfigFromFile: async (
|
loadConfigFromFile: async (
|
||||||
configFiles: ConfigFile[],
|
configFiles: ConfigFile[],
|
||||||
savedConfig: string | null
|
savedConfig: string | null
|
||||||
): Promise<string | null> => {
|
) => {
|
||||||
let currentSelectedFile = null;
|
let currentSelectedFile = null;
|
||||||
|
|
||||||
if (savedConfig) {
|
if (savedConfig) {
|
||||||
|
|
|
||||||
9
src/types/electron.d.ts
vendored
9
src/types/electron.d.ts
vendored
|
|
@ -6,6 +6,7 @@ import type {
|
||||||
} from '@/types/hardware';
|
} from '@/types/hardware';
|
||||||
import type { BackendOption, BackendSupport } from '@/types';
|
import type { BackendOption, BackendSupport } from '@/types';
|
||||||
import type { MantineColorScheme } from '@mantine/core';
|
import type { MantineColorScheme } from '@mantine/core';
|
||||||
|
import type { SystemMetrics } from '@/main/modules/monitoring';
|
||||||
|
|
||||||
export interface GitHubAsset {
|
export interface GitHubAsset {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
@ -172,6 +173,13 @@ export interface OpenWebUIAPI {
|
||||||
isUvAvailable: () => Promise<boolean>;
|
isUvAvailable: () => Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MonitoringAPI {
|
||||||
|
start: () => Promise<void>;
|
||||||
|
stop: () => Promise<void>;
|
||||||
|
onMetrics: (callback: (metrics: SystemMetrics) => void) => void;
|
||||||
|
removeMetricsListener: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
electronAPI: {
|
electronAPI: {
|
||||||
|
|
@ -181,6 +189,7 @@ declare global {
|
||||||
logs: LogsAPI;
|
logs: LogsAPI;
|
||||||
sillytavern: SillyTavernAPI;
|
sillytavern: SillyTavernAPI;
|
||||||
openwebui: OpenWebUIAPI;
|
openwebui: OpenWebUIAPI;
|
||||||
|
monitoring: MonitoringAPI;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
5
src/types/hardware.d.ts
vendored
5
src/types/hardware.d.ts
vendored
|
|
@ -34,6 +34,11 @@ export interface BasicGPUInfo {
|
||||||
gpuInfo: string[];
|
gpuInfo: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface HardwareDetectionResult {
|
||||||
|
supported: boolean;
|
||||||
|
devices: string[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface HardwareInfo {
|
export interface HardwareInfo {
|
||||||
cpu: CPUCapabilities;
|
cpu: CPUCapabilities;
|
||||||
gpu: BasicGPUInfo;
|
gpu: BasicGPUInfo;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { ASSET_SUFFIXES } from '@/constants';
|
import { ASSET_SUFFIXES } from '@/constants';
|
||||||
import { stripAssetExtensions } from '@/utils/version';
|
import { stripAssetExtensions } from '@/utils/version';
|
||||||
|
|
||||||
export const getAssetDescription = (assetName: string): string => {
|
export const getAssetDescription = (assetName: string) => {
|
||||||
const name = stripAssetExtensions(assetName).toLowerCase();
|
const name = stripAssetExtensions(assetName).toLowerCase();
|
||||||
|
|
||||||
if (name.includes(ASSET_SUFFIXES.ROCM)) {
|
if (name.includes(ASSET_SUFFIXES.ROCM)) {
|
||||||
|
|
@ -19,7 +19,7 @@ export const getAssetDescription = (assetName: string): string => {
|
||||||
return "Standard build that's ideal for most cases.";
|
return "Standard build that's ideal for most cases.";
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isAssetStandard = (assetName: string): boolean => {
|
export const isAssetStandard = (assetName: string) => {
|
||||||
const name = stripAssetExtensions(assetName).toLowerCase();
|
const name = stripAssetExtensions(assetName).toLowerCase();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -54,7 +54,7 @@ export const sortDownloadsByType = <T extends { name: string }>(
|
||||||
return a.name.localeCompare(b.name);
|
return a.name.localeCompare(b.name);
|
||||||
});
|
});
|
||||||
|
|
||||||
export const pretifyBinName = (binName: string): string => {
|
export const pretifyBinName = (binName: string) => {
|
||||||
const cleanName = stripAssetExtensions(binName).toLowerCase();
|
const cleanName = stripAssetExtensions(binName).toLowerCase();
|
||||||
|
|
||||||
if (cleanName.includes(ASSET_SUFFIXES.ROCM)) {
|
if (cleanName.includes(ASSET_SUFFIXES.ROCM)) {
|
||||||
|
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
import { ROCM } from '@/constants';
|
|
||||||
|
|
||||||
export const formatDownloadSize = (size: number, url?: string): string => {
|
|
||||||
if (!size) return '';
|
|
||||||
|
|
||||||
const isApproximateSize = url?.includes(ROCM.LINUX.DOWNLOAD_URL);
|
|
||||||
|
|
||||||
return isApproximateSize
|
|
||||||
? `~${formatFileSizeInMB(size)}`
|
|
||||||
: formatFileSizeInMB(size);
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatFileSizeInMB = (bytes: number) => {
|
|
||||||
if (bytes === 0) return '0 MB';
|
|
||||||
const mb = bytes / (1024 * 1024);
|
|
||||||
return parseFloat(mb.toFixed(1)) + ' MB';
|
|
||||||
};
|
|
||||||
47
src/utils/format.ts
Normal file
47
src/utils/format.ts
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
import { ROCM } from '@/constants';
|
||||||
|
|
||||||
|
export const formatDownloadSize = (size: number, url?: string) => {
|
||||||
|
if (!size) return '';
|
||||||
|
|
||||||
|
const isApproximateSize = url?.includes(ROCM.LINUX.DOWNLOAD_URL);
|
||||||
|
|
||||||
|
return isApproximateSize
|
||||||
|
? `~${formatFileSizeInMB(size)}`
|
||||||
|
: formatFileSizeInMB(size);
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatFileSizeInMB = (bytes: number) => {
|
||||||
|
if (bytes === 0) return '0 MB';
|
||||||
|
const mb = bytes / (1024 * 1024);
|
||||||
|
return parseFloat(mb.toFixed(1)) + ' MB';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formatBytes = (bytes: number) => {
|
||||||
|
const units = ['B', 'KB', 'MB', 'GB'];
|
||||||
|
let size = bytes;
|
||||||
|
let unitIndex = 0;
|
||||||
|
|
||||||
|
while (size >= 1024 && unitIndex < units.length - 1) {
|
||||||
|
size /= 1024;
|
||||||
|
unitIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${size.toFixed(1)} ${units[unitIndex]}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formatDeviceName = (deviceName: string) =>
|
||||||
|
deviceName
|
||||||
|
.replace(/^AMD\s+/i, '')
|
||||||
|
.replace(/^NVIDIA\s+/i, '')
|
||||||
|
.replace(/^Intel\s+/i, '')
|
||||||
|
.replace(/\s+Graphics/i, '')
|
||||||
|
.replace(/\s+GPU/i, '')
|
||||||
|
.replace(/\s+Processor/i, '')
|
||||||
|
.replace(/\s+CPU/i, '')
|
||||||
|
.replace(/GeForce\s+/i, '')
|
||||||
|
.replace(/Radeon\s+/i, '')
|
||||||
|
.replace(/\s+Series/i, '')
|
||||||
|
.replace(/\s*[([{].*?[)\]}]\s*/g, '')
|
||||||
|
.replace(/\s*\d+-core\s*/gi, '')
|
||||||
|
.replace(/\s+/g, ' ')
|
||||||
|
.trim();
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { readFile, writeFile, access, mkdir } from 'fs/promises';
|
import { readFile, writeFile, access, mkdir } from 'fs/promises';
|
||||||
import { constants } from 'fs';
|
import { constants } from 'fs';
|
||||||
|
|
||||||
export const pathExists = async (path: string): Promise<boolean> => {
|
export const pathExists = async (path: string) => {
|
||||||
try {
|
try {
|
||||||
await access(path, constants.F_OK);
|
await access(path, constants.F_OK);
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -21,15 +21,12 @@ export const readJsonFile = async <T = unknown>(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const writeJsonFile = async (
|
export const writeJsonFile = async (path: string, data: unknown) => {
|
||||||
path: string,
|
|
||||||
data: unknown
|
|
||||||
): Promise<void> => {
|
|
||||||
const content = JSON.stringify(data, null, 2);
|
const content = JSON.stringify(data, null, 2);
|
||||||
await writeFile(path, content, 'utf-8');
|
await writeFile(path, content, 'utf-8');
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ensureDir = async (path: string): Promise<void> => {
|
export const ensureDir = async (path: string) => {
|
||||||
try {
|
try {
|
||||||
await mkdir(path, { recursive: true });
|
await mkdir(path, { recursive: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
export const shortenDeviceName = (deviceName: string): string =>
|
|
||||||
deviceName
|
|
||||||
.replace(/^AMD\s+/i, '')
|
|
||||||
.replace(/^NVIDIA\s+/i, '')
|
|
||||||
.replace(/^Intel\s+/i, '')
|
|
||||||
.replace(/\s+Graphics/i, '')
|
|
||||||
.replace(/\s+GPU/i, '')
|
|
||||||
.replace(/\s+Processor/i, '')
|
|
||||||
.replace(/\s+CPU/i, '')
|
|
||||||
.replace(/GeForce\s+/i, '')
|
|
||||||
.replace(/Radeon\s+/i, '')
|
|
||||||
.replace(/\s+Series/i, '')
|
|
||||||
.replace(/\s+/g, ' ')
|
|
||||||
.trim();
|
|
||||||
|
|
@ -1,10 +1,4 @@
|
||||||
export interface KoboldConfig {
|
export function parseKoboldConfig(args: string[]) {
|
||||||
host: string;
|
|
||||||
port: number;
|
|
||||||
isImageMode: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parseKoboldConfig(args: string[]): KoboldConfig {
|
|
||||||
let host = 'localhost';
|
let host = 'localhost';
|
||||||
let port = 5001;
|
let port = 5001;
|
||||||
let hasSdModel = false;
|
let hasSdModel = false;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
const logError = (message: string, error: Error): void => {
|
export const logError = (message: string, error: Error) => {
|
||||||
if (window.electronAPI?.logs?.logError) {
|
if (window.electronAPI?.logs?.logError) {
|
||||||
window.electronAPI.logs.logError(message, error);
|
window.electronAPI.logs.logError(message, error);
|
||||||
}
|
}
|
||||||
|
|
@ -19,7 +19,7 @@ export const safeExecute = async <T>(
|
||||||
export const tryExecute = async (
|
export const tryExecute = async (
|
||||||
operation: () => Promise<void>,
|
operation: () => Promise<void>,
|
||||||
errorMessage: string
|
errorMessage: string
|
||||||
): Promise<boolean> => {
|
) => {
|
||||||
try {
|
try {
|
||||||
await operation();
|
await operation();
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -28,7 +28,3 @@ export const tryExecute = async (
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const error = (message: string, error: Error): void => {
|
|
||||||
logError(message, error);
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,11 @@ import { join } from 'path';
|
||||||
import { homedir } from 'os';
|
import { homedir } from 'os';
|
||||||
import { PRODUCT_NAME, CONFIG_FILE_NAME } from '@/constants';
|
import { PRODUCT_NAME, CONFIG_FILE_NAME } from '@/constants';
|
||||||
|
|
||||||
export function getConfigDir(): string {
|
export function getConfigDir() {
|
||||||
return join(getConfigDirPath(), CONFIG_FILE_NAME);
|
return join(getConfigDirPath(), CONFIG_FILE_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getConfigDirPath(): string {
|
function getConfigDirPath() {
|
||||||
const platform = process.platform;
|
const platform = process.platform;
|
||||||
const home = homedir();
|
const home = homedir();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,8 @@ export interface ProcessTerminationOptions {
|
||||||
async function killWindowsProcessTree(
|
async function killWindowsProcessTree(
|
||||||
pid: number,
|
pid: number,
|
||||||
logError?: (message: string, error: Error) => void
|
logError?: (message: string, error: Error) => void
|
||||||
): Promise<void> {
|
) {
|
||||||
return new Promise((resolve) => {
|
return new Promise<void>((resolve) => {
|
||||||
const taskkill = spawn('taskkill', ['/pid', pid.toString(), '/t', '/f'], {
|
const taskkill = spawn('taskkill', ['/pid', pid.toString(), '/t', '/f'], {
|
||||||
stdio: 'pipe',
|
stdio: 'pipe',
|
||||||
});
|
});
|
||||||
|
|
@ -35,7 +35,7 @@ async function killWindowsProcessTree(
|
||||||
export async function terminateProcess(
|
export async function terminateProcess(
|
||||||
childProcess: ChildProcess,
|
childProcess: ChildProcess,
|
||||||
options: ProcessTerminationOptions = {}
|
options: ProcessTerminationOptions = {}
|
||||||
): Promise<void> {
|
) {
|
||||||
const { timeoutMs = 3000, logError } = options;
|
const { timeoutMs = 3000, logError } = options;
|
||||||
|
|
||||||
if (!childProcess?.pid) {
|
if (!childProcess?.pid) {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
import { GITHUB_API, ROCM } from '@/constants';
|
import { GITHUB_API, ROCM } from '@/constants';
|
||||||
import type { DownloadItem } from '@/types/electron';
|
|
||||||
import type { GitHubAsset } from '@/types';
|
import type { GitHubAsset } from '@/types';
|
||||||
|
|
||||||
export async function getROCmDownload(): Promise<DownloadItem | null> {
|
export async function getROCmDownload() {
|
||||||
const platform = await window.electronAPI.kobold.getPlatform();
|
const platform = await window.electronAPI.kobold.getPlatform();
|
||||||
|
|
||||||
if (platform === 'linux') {
|
if (platform === 'linux') {
|
||||||
|
|
@ -14,7 +13,7 @@ export async function getROCmDownload(): Promise<DownloadItem | null> {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getLinuxROCmDownload(): Promise<DownloadItem | null> {
|
async function getLinuxROCmDownload() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(GITHUB_API.LATEST_RELEASE_URL);
|
const response = await fetch(GITHUB_API.LATEST_RELEASE_URL);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
@ -40,7 +39,7 @@ async function getLinuxROCmDownload(): Promise<DownloadItem | null> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getWindowsROCmDownload(): Promise<DownloadItem | null> {
|
async function getWindowsROCmDownload() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(GITHUB_API.ROCM_LATEST_RELEASE_URL);
|
const response = await fetch(GITHUB_API.ROCM_LATEST_RELEASE_URL);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
import { error } from '@/utils/logger';
|
import { logError } from '@/utils/logger';
|
||||||
|
|
||||||
export const handleTerminalOutput = (
|
export const handleTerminalOutput = (prevContent: string, newData: string) => {
|
||||||
prevContent: string,
|
|
||||||
newData: string
|
|
||||||
): string => {
|
|
||||||
try {
|
try {
|
||||||
if (newData.includes('\r')) {
|
if (newData.includes('\r')) {
|
||||||
const hasStandaloneCarriageReturns = /\r(?!\n)/g.test(newData);
|
const hasStandaloneCarriageReturns = /\r(?!\n)/g.test(newData);
|
||||||
|
|
@ -32,14 +29,14 @@ export const handleTerminalOutput = (
|
||||||
|
|
||||||
return prevContent + newData;
|
return prevContent + newData;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error('Terminal Basic Error', err as Error);
|
logError('Terminal Basic Error', err as Error);
|
||||||
return prevContent + newData;
|
return prevContent + newData;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const URL_REGEX = /(https?:\/\/[^\s<>"{}|\\^`[\]]+)/gi;
|
const URL_REGEX = /(https?:\/\/[^\s<>"{}|\\^`[\]]+)/gi;
|
||||||
|
|
||||||
const linkifyText = (text: string): string =>
|
const linkifyText = (text: string) =>
|
||||||
text.replace(URL_REGEX, (url) => {
|
text.replace(URL_REGEX, (url) => {
|
||||||
const cleanUrl = url.replace(/[.,;:!?]+$/, '');
|
const cleanUrl = url.replace(/[.,;:!?]+$/, '');
|
||||||
const trailingPunctuation = url.slice(cleanUrl.length);
|
const trailingPunctuation = url.slice(cleanUrl.length);
|
||||||
|
|
@ -47,7 +44,7 @@ const linkifyText = (text: string): string =>
|
||||||
return `<a href="${cleanUrl}" target="_blank" rel="noopener noreferrer" style="color: #339af0; text-decoration: underline; cursor: pointer;">${cleanUrl}</a>${trailingPunctuation}`;
|
return `<a href="${cleanUrl}" target="_blank" rel="noopener noreferrer" style="color: #339af0; text-decoration: underline; cursor: pointer;">${cleanUrl}</a>${trailingPunctuation}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
const escapeHtmlExceptLinks = (text: string): string => {
|
const escapeHtmlExceptLinks = (text: string) => {
|
||||||
const escaped = text
|
const escaped = text
|
||||||
.replace(/&/g, '&')
|
.replace(/&/g, '&')
|
||||||
.replace(/</g, '<')
|
.replace(/</g, '<')
|
||||||
|
|
@ -58,7 +55,7 @@ const escapeHtmlExceptLinks = (text: string): string => {
|
||||||
return linkifyText(escaped);
|
return linkifyText(escaped);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const processTerminalContent = (content: string): string => {
|
export const processTerminalContent = (content: string) => {
|
||||||
if (!content) return '';
|
if (!content) return '';
|
||||||
|
|
||||||
return escapeHtmlExceptLinks(content);
|
return escapeHtmlExceptLinks(content);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
const isValidUrl = (string: string): boolean => {
|
const isValidUrl = (string: string) => {
|
||||||
try {
|
try {
|
||||||
const url = new URL(string);
|
const url = new URL(string);
|
||||||
return url.protocol === 'http:' || url.protocol === 'https:';
|
return url.protocol === 'http:' || url.protocol === 'https:';
|
||||||
|
|
@ -7,7 +7,7 @@ const isValidUrl = (string: string): boolean => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const isValidFilePath = (path: string): boolean => {
|
const isValidFilePath = (path: string) => {
|
||||||
if (!path.trim()) return false;
|
if (!path.trim()) return false;
|
||||||
|
|
||||||
const validExtensions = ['.gguf'];
|
const validExtensions = ['.gguf'];
|
||||||
|
|
@ -18,9 +18,7 @@ const isValidFilePath = (path: string): boolean => {
|
||||||
return hasValidExtension || path.includes('/') || path.includes('\\');
|
return hasValidExtension || path.includes('/') || path.includes('\\');
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getInputValidationState = (
|
export const getInputValidationState = (path: string) => {
|
||||||
path: string
|
|
||||||
): 'valid' | 'invalid' | 'neutral' => {
|
|
||||||
if (!path.trim()) return 'neutral';
|
if (!path.trim()) return 'neutral';
|
||||||
|
|
||||||
if (isValidUrl(path) || isValidFilePath(path)) {
|
if (isValidUrl(path) || isValidFilePath(path)) {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
import type { InstalledVersion } from '@/types';
|
import type { InstalledVersion } from '@/types';
|
||||||
|
|
||||||
export const getDisplayNameFromPath = (
|
export const getDisplayNameFromPath = (installedVersion: InstalledVersion) => {
|
||||||
installedVersion: InstalledVersion
|
|
||||||
): string => {
|
|
||||||
const pathParts = installedVersion.path.split(/[/\\]/);
|
const pathParts = installedVersion.path.split(/[/\\]/);
|
||||||
const launcherIndex = pathParts.findIndex(
|
const launcherIndex = pathParts.findIndex(
|
||||||
(part) => part === 'koboldcpp-launcher' || part === 'koboldcpp-launcher.exe'
|
(part) => part === 'koboldcpp-launcher' || part === 'koboldcpp-launcher.exe'
|
||||||
|
|
@ -15,16 +13,16 @@ export const getDisplayNameFromPath = (
|
||||||
return installedVersion.filename;
|
return installedVersion.filename;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const stripAssetExtensions = (assetName: string): string =>
|
export const stripAssetExtensions = (assetName: string) =>
|
||||||
assetName.replace(/\.(tar\.gz|zip|exe|dmg|AppImage)$/i, '');
|
assetName.replace(/\.(tar\.gz|zip|exe|dmg|AppImage)$/i, '');
|
||||||
|
|
||||||
const stripVersionSuffix = (displayName: string): string =>
|
const stripVersionSuffix = (displayName: string) =>
|
||||||
displayName.replace(
|
displayName.replace(
|
||||||
/-(\d+\.\d+(?:\.\d+)?(?:\.[a-zA-Z0-9]+)*(?:-[a-zA-Z0-9]+)*)$/,
|
/-(\d+\.\d+(?:\.\d+)?(?:\.[a-zA-Z0-9]+)*(?:-[a-zA-Z0-9]+)*)$/,
|
||||||
''
|
''
|
||||||
);
|
);
|
||||||
|
|
||||||
export const compareVersions = (versionA: string, versionB: string): number => {
|
export const compareVersions = (versionA: string, versionB: string) => {
|
||||||
const cleanVersion = (version: string): string =>
|
const cleanVersion = (version: string): string =>
|
||||||
version.replace(/^v/, '').replace(/[^0-9.]/g, '');
|
version.replace(/^v/, '').replace(/[^0-9.]/g, '');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { ZOOM } from '@/constants';
|
import { ZOOM } from '@/constants';
|
||||||
|
|
||||||
export const zoomLevelToPercentage = (zoomLevel: number): number =>
|
export const zoomLevelToPercentage = (zoomLevel: number) =>
|
||||||
Math.round(Math.pow(1.2, zoomLevel) * 100);
|
Math.round(Math.pow(1.2, zoomLevel) * 100);
|
||||||
|
|
||||||
export const percentageToZoomLevel = (percentage: number): number =>
|
export const percentageToZoomLevel = (percentage: number) =>
|
||||||
Math.log(percentage / 100) / Math.log(1.2);
|
Math.log(percentage / 100) / Math.log(1.2);
|
||||||
|
|
||||||
export const isValidZoomPercentage = (percentage: number): boolean =>
|
export const isValidZoomPercentage = (percentage: number) =>
|
||||||
percentage >= ZOOM.MIN_PERCENTAGE && percentage <= ZOOM.MAX_PERCENTAGE;
|
percentage >= ZOOM.MIN_PERCENTAGE && percentage <= ZOOM.MAX_PERCENTAGE;
|
||||||
|
|
|
||||||
232
yarn.lock
232
yarn.lock
|
|
@ -585,13 +585,13 @@ __metadata:
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@eslint-community/eslint-utils@npm:^4.7.0, @eslint-community/eslint-utils@npm:^4.8.0":
|
"@eslint-community/eslint-utils@npm:^4.7.0, @eslint-community/eslint-utils@npm:^4.8.0":
|
||||||
version: 4.8.0
|
version: 4.9.0
|
||||||
resolution: "@eslint-community/eslint-utils@npm:4.8.0"
|
resolution: "@eslint-community/eslint-utils@npm:4.9.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
eslint-visitor-keys: "npm:^3.4.3"
|
eslint-visitor-keys: "npm:^3.4.3"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
|
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
|
||||||
checksum: 10c0/33b93d2a4e9d5fe4c11d02d0fc5ed69e12fcb1e7ca031ded0d6adb24e768c36df77288ed79eecc784f9db34219816247db27688dfe869fb7fbf096840a097d7a
|
checksum: 10c0/8881e22d519326e7dba85ea915ac7a143367c805e6ba1374c987aa2fbdd09195cc51183d2da72c0e2ff388f84363e1b220fd0d19bef10c272c63455162176817
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
@ -864,9 +864,9 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@mantine/core@npm:^8.3.0":
|
"@mantine/core@npm:^8.3.1":
|
||||||
version: 8.3.0
|
version: 8.3.1
|
||||||
resolution: "@mantine/core@npm:8.3.0"
|
resolution: "@mantine/core@npm:8.3.1"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@floating-ui/react": "npm:^0.27.16"
|
"@floating-ui/react": "npm:^0.27.16"
|
||||||
clsx: "npm:^2.1.1"
|
clsx: "npm:^2.1.1"
|
||||||
|
|
@ -875,19 +875,19 @@ __metadata:
|
||||||
react-textarea-autosize: "npm:8.5.9"
|
react-textarea-autosize: "npm:8.5.9"
|
||||||
type-fest: "npm:^4.41.0"
|
type-fest: "npm:^4.41.0"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
"@mantine/hooks": 8.3.0
|
"@mantine/hooks": 8.3.1
|
||||||
react: ^18.x || ^19.x
|
react: ^18.x || ^19.x
|
||||||
react-dom: ^18.x || ^19.x
|
react-dom: ^18.x || ^19.x
|
||||||
checksum: 10c0/28430c11c726af9e5b992bc195614cc67a8d1a4d25f3ced6753ab0399d67c6bb927c1b5eb4f383f8f75ef09eec3b17998761ab5983c850459d7cf34e75d0688b
|
checksum: 10c0/6faab4553d35e3f676e852ea9b926e0d3b744eda5b38d450eb3bdb89c92cdd2ce8ae9fd7e4b47c83fc870d215a62c8fa3299d25fcaa83a07f67c782883a8ca30
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@mantine/hooks@npm:^8.3.0":
|
"@mantine/hooks@npm:^8.3.1":
|
||||||
version: 8.3.0
|
version: 8.3.1
|
||||||
resolution: "@mantine/hooks@npm:8.3.0"
|
resolution: "@mantine/hooks@npm:8.3.1"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^18.x || ^19.x
|
react: ^18.x || ^19.x
|
||||||
checksum: 10c0/2caaac7654a97424e798d3ae18ee9c70219a62d69dbd21582da90f44b8381674ed496583e4e7ad378a2fd42b7a78fb84534982297918acb24ed674abcb1a312b
|
checksum: 10c0/d64ea9c848a687668ce34007e3ab226b5c45c52ea1f4bdd721ca17b07d7f7a642b86e23ee65777179d6cd1ae9c3ce56fe43602828f332bfc2cbd59658b1c9047
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
@ -974,149 +974,149 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@rollup/rollup-android-arm-eabi@npm:4.50.0":
|
"@rollup/rollup-android-arm-eabi@npm:4.50.1":
|
||||||
version: 4.50.0
|
version: 4.50.1
|
||||||
resolution: "@rollup/rollup-android-arm-eabi@npm:4.50.0"
|
resolution: "@rollup/rollup-android-arm-eabi@npm:4.50.1"
|
||||||
conditions: os=android & cpu=arm
|
conditions: os=android & cpu=arm
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@rollup/rollup-android-arm64@npm:4.50.0":
|
"@rollup/rollup-android-arm64@npm:4.50.1":
|
||||||
version: 4.50.0
|
version: 4.50.1
|
||||||
resolution: "@rollup/rollup-android-arm64@npm:4.50.0"
|
resolution: "@rollup/rollup-android-arm64@npm:4.50.1"
|
||||||
conditions: os=android & cpu=arm64
|
conditions: os=android & cpu=arm64
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@rollup/rollup-darwin-arm64@npm:4.50.0":
|
"@rollup/rollup-darwin-arm64@npm:4.50.1":
|
||||||
version: 4.50.0
|
version: 4.50.1
|
||||||
resolution: "@rollup/rollup-darwin-arm64@npm:4.50.0"
|
resolution: "@rollup/rollup-darwin-arm64@npm:4.50.1"
|
||||||
conditions: os=darwin & cpu=arm64
|
conditions: os=darwin & cpu=arm64
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@rollup/rollup-darwin-x64@npm:4.50.0":
|
"@rollup/rollup-darwin-x64@npm:4.50.1":
|
||||||
version: 4.50.0
|
version: 4.50.1
|
||||||
resolution: "@rollup/rollup-darwin-x64@npm:4.50.0"
|
resolution: "@rollup/rollup-darwin-x64@npm:4.50.1"
|
||||||
conditions: os=darwin & cpu=x64
|
conditions: os=darwin & cpu=x64
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@rollup/rollup-freebsd-arm64@npm:4.50.0":
|
"@rollup/rollup-freebsd-arm64@npm:4.50.1":
|
||||||
version: 4.50.0
|
version: 4.50.1
|
||||||
resolution: "@rollup/rollup-freebsd-arm64@npm:4.50.0"
|
resolution: "@rollup/rollup-freebsd-arm64@npm:4.50.1"
|
||||||
conditions: os=freebsd & cpu=arm64
|
conditions: os=freebsd & cpu=arm64
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@rollup/rollup-freebsd-x64@npm:4.50.0":
|
"@rollup/rollup-freebsd-x64@npm:4.50.1":
|
||||||
version: 4.50.0
|
version: 4.50.1
|
||||||
resolution: "@rollup/rollup-freebsd-x64@npm:4.50.0"
|
resolution: "@rollup/rollup-freebsd-x64@npm:4.50.1"
|
||||||
conditions: os=freebsd & cpu=x64
|
conditions: os=freebsd & cpu=x64
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@rollup/rollup-linux-arm-gnueabihf@npm:4.50.0":
|
"@rollup/rollup-linux-arm-gnueabihf@npm:4.50.1":
|
||||||
version: 4.50.0
|
version: 4.50.1
|
||||||
resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.50.0"
|
resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.50.1"
|
||||||
conditions: os=linux & cpu=arm & libc=glibc
|
conditions: os=linux & cpu=arm & libc=glibc
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@rollup/rollup-linux-arm-musleabihf@npm:4.50.0":
|
"@rollup/rollup-linux-arm-musleabihf@npm:4.50.1":
|
||||||
version: 4.50.0
|
version: 4.50.1
|
||||||
resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.50.0"
|
resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.50.1"
|
||||||
conditions: os=linux & cpu=arm & libc=musl
|
conditions: os=linux & cpu=arm & libc=musl
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@rollup/rollup-linux-arm64-gnu@npm:4.50.0":
|
"@rollup/rollup-linux-arm64-gnu@npm:4.50.1":
|
||||||
version: 4.50.0
|
version: 4.50.1
|
||||||
resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.50.0"
|
resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.50.1"
|
||||||
conditions: os=linux & cpu=arm64 & libc=glibc
|
conditions: os=linux & cpu=arm64 & libc=glibc
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@rollup/rollup-linux-arm64-musl@npm:4.50.0":
|
"@rollup/rollup-linux-arm64-musl@npm:4.50.1":
|
||||||
version: 4.50.0
|
version: 4.50.1
|
||||||
resolution: "@rollup/rollup-linux-arm64-musl@npm:4.50.0"
|
resolution: "@rollup/rollup-linux-arm64-musl@npm:4.50.1"
|
||||||
conditions: os=linux & cpu=arm64 & libc=musl
|
conditions: os=linux & cpu=arm64 & libc=musl
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@rollup/rollup-linux-loongarch64-gnu@npm:4.50.0":
|
"@rollup/rollup-linux-loongarch64-gnu@npm:4.50.1":
|
||||||
version: 4.50.0
|
version: 4.50.1
|
||||||
resolution: "@rollup/rollup-linux-loongarch64-gnu@npm:4.50.0"
|
resolution: "@rollup/rollup-linux-loongarch64-gnu@npm:4.50.1"
|
||||||
conditions: os=linux & cpu=loong64 & libc=glibc
|
conditions: os=linux & cpu=loong64 & libc=glibc
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@rollup/rollup-linux-ppc64-gnu@npm:4.50.0":
|
"@rollup/rollup-linux-ppc64-gnu@npm:4.50.1":
|
||||||
version: 4.50.0
|
version: 4.50.1
|
||||||
resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.50.0"
|
resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.50.1"
|
||||||
conditions: os=linux & cpu=ppc64 & libc=glibc
|
conditions: os=linux & cpu=ppc64 & libc=glibc
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@rollup/rollup-linux-riscv64-gnu@npm:4.50.0":
|
"@rollup/rollup-linux-riscv64-gnu@npm:4.50.1":
|
||||||
version: 4.50.0
|
version: 4.50.1
|
||||||
resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.50.0"
|
resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.50.1"
|
||||||
conditions: os=linux & cpu=riscv64 & libc=glibc
|
conditions: os=linux & cpu=riscv64 & libc=glibc
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@rollup/rollup-linux-riscv64-musl@npm:4.50.0":
|
"@rollup/rollup-linux-riscv64-musl@npm:4.50.1":
|
||||||
version: 4.50.0
|
version: 4.50.1
|
||||||
resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.50.0"
|
resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.50.1"
|
||||||
conditions: os=linux & cpu=riscv64 & libc=musl
|
conditions: os=linux & cpu=riscv64 & libc=musl
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@rollup/rollup-linux-s390x-gnu@npm:4.50.0":
|
"@rollup/rollup-linux-s390x-gnu@npm:4.50.1":
|
||||||
version: 4.50.0
|
version: 4.50.1
|
||||||
resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.50.0"
|
resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.50.1"
|
||||||
conditions: os=linux & cpu=s390x & libc=glibc
|
conditions: os=linux & cpu=s390x & libc=glibc
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@rollup/rollup-linux-x64-gnu@npm:4.50.0":
|
"@rollup/rollup-linux-x64-gnu@npm:4.50.1":
|
||||||
version: 4.50.0
|
version: 4.50.1
|
||||||
resolution: "@rollup/rollup-linux-x64-gnu@npm:4.50.0"
|
resolution: "@rollup/rollup-linux-x64-gnu@npm:4.50.1"
|
||||||
conditions: os=linux & cpu=x64 & libc=glibc
|
conditions: os=linux & cpu=x64 & libc=glibc
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@rollup/rollup-linux-x64-musl@npm:4.50.0":
|
"@rollup/rollup-linux-x64-musl@npm:4.50.1":
|
||||||
version: 4.50.0
|
version: 4.50.1
|
||||||
resolution: "@rollup/rollup-linux-x64-musl@npm:4.50.0"
|
resolution: "@rollup/rollup-linux-x64-musl@npm:4.50.1"
|
||||||
conditions: os=linux & cpu=x64 & libc=musl
|
conditions: os=linux & cpu=x64 & libc=musl
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@rollup/rollup-openharmony-arm64@npm:4.50.0":
|
"@rollup/rollup-openharmony-arm64@npm:4.50.1":
|
||||||
version: 4.50.0
|
version: 4.50.1
|
||||||
resolution: "@rollup/rollup-openharmony-arm64@npm:4.50.0"
|
resolution: "@rollup/rollup-openharmony-arm64@npm:4.50.1"
|
||||||
conditions: os=openharmony & cpu=arm64
|
conditions: os=openharmony & cpu=arm64
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@rollup/rollup-win32-arm64-msvc@npm:4.50.0":
|
"@rollup/rollup-win32-arm64-msvc@npm:4.50.1":
|
||||||
version: 4.50.0
|
version: 4.50.1
|
||||||
resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.50.0"
|
resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.50.1"
|
||||||
conditions: os=win32 & cpu=arm64
|
conditions: os=win32 & cpu=arm64
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@rollup/rollup-win32-ia32-msvc@npm:4.50.0":
|
"@rollup/rollup-win32-ia32-msvc@npm:4.50.1":
|
||||||
version: 4.50.0
|
version: 4.50.1
|
||||||
resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.50.0"
|
resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.50.1"
|
||||||
conditions: os=win32 & cpu=ia32
|
conditions: os=win32 & cpu=ia32
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@rollup/rollup-win32-x64-msvc@npm:4.50.0":
|
"@rollup/rollup-win32-x64-msvc@npm:4.50.1":
|
||||||
version: 4.50.0
|
version: 4.50.1
|
||||||
resolution: "@rollup/rollup-win32-x64-msvc@npm:4.50.0"
|
resolution: "@rollup/rollup-win32-x64-msvc@npm:4.50.1"
|
||||||
conditions: os=win32 & cpu=x64
|
conditions: os=win32 & cpu=x64
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
@ -1614,9 +1614,9 @@ __metadata:
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"ansi-regex@npm:^6.0.1":
|
"ansi-regex@npm:^6.0.1":
|
||||||
version: 6.2.0
|
version: 6.2.2
|
||||||
resolution: "ansi-regex@npm:6.2.0"
|
resolution: "ansi-regex@npm:6.2.2"
|
||||||
checksum: 10c0/20a2e55ae9816074a60e6729dbe3daad664cd967fc82acc08b02f5677db84baa688babf940d71f50acbbb184c02459453789705e079f4d521166ae66451de551
|
checksum: 10c0/05d4acb1d2f59ab2cf4b794339c7b168890d44dda4bf0ce01152a8da0213aca207802f930442ce8cd22d7a92f44907664aac6508904e75e038fa944d2601b30f
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
@ -1630,9 +1630,9 @@ __metadata:
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"ansi-styles@npm:^6.1.0":
|
"ansi-styles@npm:^6.1.0":
|
||||||
version: 6.2.1
|
version: 6.2.3
|
||||||
resolution: "ansi-styles@npm:6.2.1"
|
resolution: "ansi-styles@npm:6.2.3"
|
||||||
checksum: 10c0/5d1ec38c123984bcedd996eac680d548f31828bd679a66db2bdf11844634dde55fec3efa9c6bb1d89056a5e79c1ac540c4c784d592ea1d25028a92227d2f2d5c
|
checksum: 10c0/23b8a4ce14e18fb854693b95351e286b771d23d8844057ed2e7d083cd3e708376c3323707ec6a24365f7d7eda3ca00327fe04092e29e551499ec4c8b7bfac868
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
@ -2670,9 +2670,9 @@ __metadata:
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"electron-to-chromium@npm:^1.5.211":
|
"electron-to-chromium@npm:^1.5.211":
|
||||||
version: 1.5.214
|
version: 1.5.215
|
||||||
resolution: "electron-to-chromium@npm:1.5.214"
|
resolution: "electron-to-chromium@npm:1.5.215"
|
||||||
checksum: 10c0/76ca22fd97a2dad84a710915b5984263b31e61c7883cd3ec0c11c0d7beb3fa628780cdfd05a96ec79a904ea1c910cf02c513db60f31b627c96743e50f6b11a2e
|
checksum: 10c0/3a45976d1193e57284533096b3bbec218a5d4d85af4f7c133522aae35b14bbf22734f48ccc3f0e43a451441ebc375fa2f4350390fd729dcedb97543692133e39
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
@ -3627,8 +3627,8 @@ __metadata:
|
||||||
resolution: "gerbil@workspace:."
|
resolution: "gerbil@workspace:."
|
||||||
dependencies:
|
dependencies:
|
||||||
"@eslint/js": "npm:^9.35.0"
|
"@eslint/js": "npm:^9.35.0"
|
||||||
"@mantine/core": "npm:^8.3.0"
|
"@mantine/core": "npm:^8.3.1"
|
||||||
"@mantine/hooks": "npm:^8.3.0"
|
"@mantine/hooks": "npm:^8.3.1"
|
||||||
"@types/node": "npm:^24.3.1"
|
"@types/node": "npm:^24.3.1"
|
||||||
"@types/react": "npm:^19.1.12"
|
"@types/react": "npm:^19.1.12"
|
||||||
"@types/react-dom": "npm:^19.1.9"
|
"@types/react-dom": "npm:^19.1.9"
|
||||||
|
|
@ -4777,11 +4777,11 @@ __metadata:
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"magic-string@npm:^0.30.17":
|
"magic-string@npm:^0.30.17":
|
||||||
version: 0.30.18
|
version: 0.30.19
|
||||||
resolution: "magic-string@npm:0.30.18"
|
resolution: "magic-string@npm:0.30.19"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@jridgewell/sourcemap-codec": "npm:^1.5.5"
|
"@jridgewell/sourcemap-codec": "npm:^1.5.5"
|
||||||
checksum: 10c0/80fba01e13ce1f5c474a0498a5aa462fa158eb56567310747089a0033e432d83a2021ee2c109ac116010cd9dcf90a5231d89fbe3858165f73c00a50a74dbefcd
|
checksum: 10c0/db23fd2e2ee98a1aeb88a4cdb2353137fcf05819b883c856dd79e4c7dfb25151e2a5a4d5dbd88add5e30ed8ae5c51bcf4accbc6becb75249d924ec7b4fbcae27
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
@ -6027,30 +6027,30 @@ __metadata:
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"rollup@npm:^4.43.0":
|
"rollup@npm:^4.43.0":
|
||||||
version: 4.50.0
|
version: 4.50.1
|
||||||
resolution: "rollup@npm:4.50.0"
|
resolution: "rollup@npm:4.50.1"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@rollup/rollup-android-arm-eabi": "npm:4.50.0"
|
"@rollup/rollup-android-arm-eabi": "npm:4.50.1"
|
||||||
"@rollup/rollup-android-arm64": "npm:4.50.0"
|
"@rollup/rollup-android-arm64": "npm:4.50.1"
|
||||||
"@rollup/rollup-darwin-arm64": "npm:4.50.0"
|
"@rollup/rollup-darwin-arm64": "npm:4.50.1"
|
||||||
"@rollup/rollup-darwin-x64": "npm:4.50.0"
|
"@rollup/rollup-darwin-x64": "npm:4.50.1"
|
||||||
"@rollup/rollup-freebsd-arm64": "npm:4.50.0"
|
"@rollup/rollup-freebsd-arm64": "npm:4.50.1"
|
||||||
"@rollup/rollup-freebsd-x64": "npm:4.50.0"
|
"@rollup/rollup-freebsd-x64": "npm:4.50.1"
|
||||||
"@rollup/rollup-linux-arm-gnueabihf": "npm:4.50.0"
|
"@rollup/rollup-linux-arm-gnueabihf": "npm:4.50.1"
|
||||||
"@rollup/rollup-linux-arm-musleabihf": "npm:4.50.0"
|
"@rollup/rollup-linux-arm-musleabihf": "npm:4.50.1"
|
||||||
"@rollup/rollup-linux-arm64-gnu": "npm:4.50.0"
|
"@rollup/rollup-linux-arm64-gnu": "npm:4.50.1"
|
||||||
"@rollup/rollup-linux-arm64-musl": "npm:4.50.0"
|
"@rollup/rollup-linux-arm64-musl": "npm:4.50.1"
|
||||||
"@rollup/rollup-linux-loongarch64-gnu": "npm:4.50.0"
|
"@rollup/rollup-linux-loongarch64-gnu": "npm:4.50.1"
|
||||||
"@rollup/rollup-linux-ppc64-gnu": "npm:4.50.0"
|
"@rollup/rollup-linux-ppc64-gnu": "npm:4.50.1"
|
||||||
"@rollup/rollup-linux-riscv64-gnu": "npm:4.50.0"
|
"@rollup/rollup-linux-riscv64-gnu": "npm:4.50.1"
|
||||||
"@rollup/rollup-linux-riscv64-musl": "npm:4.50.0"
|
"@rollup/rollup-linux-riscv64-musl": "npm:4.50.1"
|
||||||
"@rollup/rollup-linux-s390x-gnu": "npm:4.50.0"
|
"@rollup/rollup-linux-s390x-gnu": "npm:4.50.1"
|
||||||
"@rollup/rollup-linux-x64-gnu": "npm:4.50.0"
|
"@rollup/rollup-linux-x64-gnu": "npm:4.50.1"
|
||||||
"@rollup/rollup-linux-x64-musl": "npm:4.50.0"
|
"@rollup/rollup-linux-x64-musl": "npm:4.50.1"
|
||||||
"@rollup/rollup-openharmony-arm64": "npm:4.50.0"
|
"@rollup/rollup-openharmony-arm64": "npm:4.50.1"
|
||||||
"@rollup/rollup-win32-arm64-msvc": "npm:4.50.0"
|
"@rollup/rollup-win32-arm64-msvc": "npm:4.50.1"
|
||||||
"@rollup/rollup-win32-ia32-msvc": "npm:4.50.0"
|
"@rollup/rollup-win32-ia32-msvc": "npm:4.50.1"
|
||||||
"@rollup/rollup-win32-x64-msvc": "npm:4.50.0"
|
"@rollup/rollup-win32-x64-msvc": "npm:4.50.1"
|
||||||
"@types/estree": "npm:1.0.8"
|
"@types/estree": "npm:1.0.8"
|
||||||
fsevents: "npm:~2.3.2"
|
fsevents: "npm:~2.3.2"
|
||||||
dependenciesMeta:
|
dependenciesMeta:
|
||||||
|
|
@ -6100,7 +6100,7 @@ __metadata:
|
||||||
optional: true
|
optional: true
|
||||||
bin:
|
bin:
|
||||||
rollup: dist/bin/rollup
|
rollup: dist/bin/rollup
|
||||||
checksum: 10c0/8a9aa0885b61ee08315cdc097fb9f97b626a0992546a6857d7668f4690a7344d8497fa7504e4dba03acefc77f03d90fec3c11bfa80777177e76ea5d8c18ddc97
|
checksum: 10c0/2029282826d5fb4e308be261b2c28329a4d2bd34304cc3960da69fd21d5acccd0267d6770b1656ffc8f166203ef7e865b4583d5f842a519c8ef059ac71854205
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
@ -6618,11 +6618,11 @@ __metadata:
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"strip-ansi@npm:^7.0.1":
|
"strip-ansi@npm:^7.0.1":
|
||||||
version: 7.1.0
|
version: 7.1.2
|
||||||
resolution: "strip-ansi@npm:7.1.0"
|
resolution: "strip-ansi@npm:7.1.2"
|
||||||
dependencies:
|
dependencies:
|
||||||
ansi-regex: "npm:^6.0.1"
|
ansi-regex: "npm:^6.0.1"
|
||||||
checksum: 10c0/a198c3762e8832505328cbf9e8c8381de14a4fa50a4f9b2160138158ea88c0f5549fb50cb13c651c3088f47e63a108b34622ec18c0499b6c8c3a5ddf6b305ac4
|
checksum: 10c0/0d6d7a023de33368fd042aab0bf48f4f4077abdfd60e5393e73c7c411e85e1b3a83507c11af2e656188511475776215df9ca589b4da2295c9455cc399ce1858b
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue