mirror of
https://github.com/lone-cloud/gerbil
synced 2026-06-03 09:33:10 -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",
|
||||
"productName": "Gerbil",
|
||||
"version": "1.1.0",
|
||||
"version": "1.1.1",
|
||||
"description": "Run Large Language Models locally",
|
||||
"main": "out/main/index.js",
|
||||
"homepage": "./",
|
||||
|
|
@ -63,8 +63,8 @@
|
|||
"vite": "^7.1.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mantine/core": "^8.3.0",
|
||||
"@mantine/hooks": "^8.3.0",
|
||||
"@mantine/core": "^8.3.1",
|
||||
"@mantine/hooks": "^8.3.1",
|
||||
"axios": "^1.11.0",
|
||||
"execa": "^9.6.0",
|
||||
"lucide-react": "^0.543.0",
|
||||
|
|
|
|||
32
src/App.tsx
32
src/App.tsx
|
|
@ -5,15 +5,15 @@ import { LaunchScreen } from '@/components/screens/Launch';
|
|||
import { InterfaceScreen } from '@/components/screens/Interface';
|
||||
import { WelcomeScreen } from '@/components/screens/Welcome';
|
||||
import { UpdateAvailableModal } from '@/components/UpdateAvailableModal';
|
||||
import { SettingsModal } from '@/components/settings/SettingsModal';
|
||||
import { EjectConfirmModal } from '@/components/EjectConfirmModal';
|
||||
import { ScreenTransition } from '@/components/ScreenTransition';
|
||||
import { TitleBar } from '@/components/TitleBar';
|
||||
import { StatusBar } from '@/components/StatusBar';
|
||||
import { ErrorBoundary } from '@/components/ErrorBoundary';
|
||||
import { useUpdateChecker } from '@/hooks/useUpdateChecker';
|
||||
import { useKoboldVersions } from '@/hooks/useKoboldVersions';
|
||||
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 { InterfaceTab, FrontendPreference, Screen } from '@/types';
|
||||
|
||||
|
|
@ -24,7 +24,6 @@ export const App = () => {
|
|||
useState<InterfaceTab>('terminal');
|
||||
const [frontendPreference, setFrontendPreference] =
|
||||
useState<FrontendPreference>('koboldcpp');
|
||||
const [settingsModalOpen, setSettingsModalOpen] = useState(false);
|
||||
const [ejectConfirmModalOpen, setEjectConfirmModalOpen] = useState(false);
|
||||
|
||||
const {
|
||||
|
|
@ -146,6 +145,7 @@ export const App = () => {
|
|||
return (
|
||||
<AppShell
|
||||
header={{ height: TITLEBAR_HEIGHT }}
|
||||
footer={{ height: STATUSBAR_HEIGHT }}
|
||||
padding={currentScreen === 'interface' ? 0 : 'md'}
|
||||
>
|
||||
<TitleBar
|
||||
|
|
@ -153,7 +153,6 @@ export const App = () => {
|
|||
currentTab={activeInterfaceTab}
|
||||
onTabChange={setActiveInterfaceTab}
|
||||
onEject={handleEject}
|
||||
onOpenSettings={() => setSettingsModalOpen(true)}
|
||||
frontendPreference={frontendPreference}
|
||||
/>
|
||||
|
||||
|
|
@ -219,22 +218,15 @@ export const App = () => {
|
|||
}
|
||||
/>
|
||||
</AppShell.Main>
|
||||
<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:'
|
||||
);
|
||||
setFrontendPreference(preference || 'koboldcpp');
|
||||
}}
|
||||
currentScreen={currentScreen || undefined}
|
||||
/>
|
||||
|
||||
<AppShell.Footer>
|
||||
<StatusBar
|
||||
currentScreen={currentScreen}
|
||||
frontendPreference={frontendPreference}
|
||||
onFrontendPreferenceChange={setFrontendPreference}
|
||||
/>
|
||||
</AppShell.Footer>
|
||||
|
||||
<EjectConfirmModal
|
||||
opened={ejectConfirmModalOpen}
|
||||
onClose={() => setEjectConfirmModalOpen(false)}
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ export const DownloadCard = ({
|
|||
onUpdate,
|
||||
}: DownloadCardProps) => {
|
||||
const computedColorScheme = useComputedColorScheme('light', {
|
||||
getInitialValueInEffect: false,
|
||||
getInitialValueInEffect: true,
|
||||
});
|
||||
const isDark = computedColorScheme === 'dark';
|
||||
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,
|
||||
AppShell,
|
||||
} from '@mantine/core';
|
||||
import {
|
||||
Minus,
|
||||
Square,
|
||||
X,
|
||||
Settings,
|
||||
Copy,
|
||||
CircleFadingArrowUp,
|
||||
} from 'lucide-react';
|
||||
import { Minus, Square, X, Copy, CircleFadingArrowUp } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { soundAssets, playSound, initializeAudio } from '@/utils/sounds';
|
||||
import { useAppUpdateChecker } from '@/hooks/useAppUpdateChecker';
|
||||
|
|
@ -28,7 +21,6 @@ interface TitleBarProps {
|
|||
currentTab: InterfaceTab;
|
||||
onTabChange: (tab: InterfaceTab) => void;
|
||||
onEject: () => void;
|
||||
onOpenSettings: () => void;
|
||||
frontendPreference: FrontendPreference;
|
||||
}
|
||||
|
||||
|
|
@ -37,11 +29,10 @@ export const TitleBar = ({
|
|||
currentTab,
|
||||
onTabChange,
|
||||
onEject,
|
||||
onOpenSettings,
|
||||
frontendPreference,
|
||||
}: TitleBarProps) => {
|
||||
const computedColorScheme = useComputedColorScheme('light', {
|
||||
getInitialValueInEffect: false,
|
||||
getInitialValueInEffect: true,
|
||||
});
|
||||
const { hasUpdate, releaseUrl } = useAppUpdateChecker();
|
||||
const { isImageGenerationMode } = useLaunchConfigStore();
|
||||
|
|
@ -234,30 +225,6 @@ export const TitleBar = ({
|
|||
</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" />,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { useState, useCallback, useEffect, useRef } from 'react';
|
|||
import { Card, Text, Title, Loader, Stack, Container } from '@mantine/core';
|
||||
import { DownloadCard } from '@/components/DownloadCard';
|
||||
import { getPlatformDisplayName } from '@/utils/platform';
|
||||
import { formatDownloadSize } from '@/utils/download';
|
||||
import { formatDownloadSize } from '@/utils/format';
|
||||
import { getAssetDescription } from '@/utils/assets';
|
||||
import { useKoboldVersions } from '@/hooks/useKoboldVersions';
|
||||
import { safeExecute } from '@/utils/logger';
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ export const TerminalTab = forwardRef<TerminalTabRef, TerminalTabProps>(
|
|||
({ onServerReady, frontendPreference = 'koboldcpp' }, ref) => {
|
||||
const { host, port, isImageGenerationMode } = useLaunchConfigStore();
|
||||
const computedColorScheme = useComputedColorScheme('light', {
|
||||
getInitialValueInEffect: false,
|
||||
getInitialValueInEffect: true,
|
||||
});
|
||||
const [terminalContent, setTerminalContent] = useState<string>('');
|
||||
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';
|
||||
|
||||
type BackendSelectItemProps = Omit<BackendOption, 'value'>;
|
||||
|
|
@ -9,14 +9,16 @@ export const BackendSelectItem = ({
|
|||
disabled = false,
|
||||
}: BackendSelectItemProps) => (
|
||||
<Group justify="space-between" wrap="nowrap">
|
||||
<Text size="sm" truncate>
|
||||
{label}
|
||||
{disabled && (
|
||||
<Text component="span" size="xs" ml="xs">
|
||||
(Compatible devices not found)
|
||||
</Text>
|
||||
)}
|
||||
</Text>
|
||||
<Box w={!disabled ? '3rem' : 'auto'}>
|
||||
<Text size="sm" truncate>
|
||||
{label}
|
||||
{disabled && (
|
||||
<Text component="span" size="xs" ml="xs">
|
||||
(Compatible devices not found)
|
||||
</Text>
|
||||
)}
|
||||
</Text>
|
||||
</Box>
|
||||
{devices && devices.length > 0 && (
|
||||
<Group gap={4}>
|
||||
{devices.slice(0, 2).map((device, index) => (
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Card, Container, Stack, Tabs, Group, Button } from '@mantine/core';
|
||||
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 { useLaunchLogic } from '@/hooks/useLaunchLogic';
|
||||
import { useWarnings } from '@/hooks/useWarnings';
|
||||
|
|
@ -197,7 +197,7 @@ export const LaunchScreen = ({ onLaunch }: LaunchScreenProps) => {
|
|||
setSelectedFile(fullConfigName);
|
||||
await window.electronAPI.kobold.setSelectedConfig(fullConfigName);
|
||||
} else {
|
||||
error(
|
||||
logError(
|
||||
'Failed to create new configuration',
|
||||
new Error('Save operation failed')
|
||||
);
|
||||
|
|
@ -207,7 +207,7 @@ export const LaunchScreen = ({ onLaunch }: LaunchScreenProps) => {
|
|||
|
||||
const handleSaveConfig = async () => {
|
||||
if (!selectedFile) {
|
||||
error(
|
||||
logError(
|
||||
'No configuration file selected for saving',
|
||||
new Error('Selected file is null')
|
||||
);
|
||||
|
|
@ -221,7 +221,7 @@ export const LaunchScreen = ({ onLaunch }: LaunchScreenProps) => {
|
|||
);
|
||||
|
||||
if (!saveSuccess) {
|
||||
error(
|
||||
logError(
|
||||
'Failed to save configuration',
|
||||
new Error('Save operation failed')
|
||||
);
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import { ZOOM } from '@/constants';
|
|||
export const AppearanceTab = () => {
|
||||
const { colorScheme, setColorScheme } = useMantineColorScheme();
|
||||
const computedColorScheme = useComputedColorScheme('light', {
|
||||
getInitialValueInEffect: false,
|
||||
getInitialValueInEffect: true,
|
||||
});
|
||||
const isDark = computedColorScheme === 'dark';
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import {
|
|||
compareVersions,
|
||||
} from '@/utils/version';
|
||||
import { safeExecute } from '@/utils/logger';
|
||||
import { formatDownloadSize } from '@/utils/download';
|
||||
import { formatDownloadSize } from '@/utils/format';
|
||||
|
||||
import { useKoboldVersions } from '@/hooks/useKoboldVersions';
|
||||
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 STATUSBAR_HEIGHT = '1.5rem';
|
||||
|
||||
export const MODAL_STYLES_WITH_TITLEBAR = {
|
||||
overlay: {
|
||||
top: TITLEBAR_HEIGHT,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useState, useCallback, useEffect } from 'react';
|
||||
import { error } from '@/utils/logger';
|
||||
import { logError } from '@/utils/logger';
|
||||
import { compareVersions } from '@/utils/version';
|
||||
import { GITHUB_API } from '@/constants';
|
||||
|
||||
|
|
@ -50,7 +50,7 @@ export const useAppUpdateChecker = () => {
|
|||
|
||||
return updateInfo;
|
||||
} catch (err) {
|
||||
error('Failed to check for app updates:', err as Error);
|
||||
logError('Failed to check for app updates:', err as Error);
|
||||
return null;
|
||||
} finally {
|
||||
setIsChecking(false);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { error } from '@/utils/logger';
|
||||
import { logError } from '@/utils/logger';
|
||||
import { getROCmDownload } from '@/utils/rocm';
|
||||
import { GITHUB_API } from '@/constants';
|
||||
import { filterAssetsByPlatform } from '@/utils/platform';
|
||||
|
|
@ -48,7 +48,7 @@ const saveToCache = (releases: DownloadItem[]) => {
|
|||
};
|
||||
localStorage.setItem(CACHE_KEY, JSON.stringify(data));
|
||||
} 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,
|
||||
};
|
||||
} 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;
|
||||
}
|
||||
};
|
||||
|
|
@ -198,7 +198,7 @@ export const useKoboldVersions = (): UseKoboldVersionsReturn => {
|
|||
|
||||
setAvailableDownloads(sortDownloadsByType(allDownloads));
|
||||
} catch (err) {
|
||||
error('Failed to load remote versions:', err as Error);
|
||||
logError('Failed to load remote versions:', err as Error);
|
||||
const cached = loadFromCache();
|
||||
if (cached) {
|
||||
const rocm = await getROCmDownload().catch(() => null);
|
||||
|
|
@ -236,7 +236,7 @@ export const useKoboldVersions = (): UseKoboldVersionsReturn => {
|
|||
|
||||
return result.success !== false;
|
||||
} catch (err) {
|
||||
error('Failed to download ${item.name}:', err as Error);
|
||||
logError(`Failed to download ${item.name}:`, err as Error);
|
||||
return false;
|
||||
} finally {
|
||||
setDownloading(null);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useState, useCallback } from 'react';
|
||||
import { error } from '@/utils/logger';
|
||||
import { logError } from '@/utils/logger';
|
||||
import type { SdConvDirectMode } from '@/types';
|
||||
|
||||
interface UseLaunchLogicProps {
|
||||
|
|
@ -283,7 +283,7 @@ export const useLaunchLogic = ({
|
|||
);
|
||||
}
|
||||
} catch (err) {
|
||||
error('Error launching:', err as Error);
|
||||
logError('Error launching:', err as Error);
|
||||
} finally {
|
||||
setIsLaunching(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useState, useCallback, useEffect } from 'react';
|
||||
import { error } from '@/utils/logger';
|
||||
import { logError } from '@/utils/logger';
|
||||
import {
|
||||
getDisplayNameFromPath,
|
||||
compareVersions,
|
||||
|
|
@ -36,7 +36,7 @@ export const useUpdateChecker = () => {
|
|||
}
|
||||
setDismissedUpdatesLoaded(true);
|
||||
} catch (err) {
|
||||
error('Failed to load dismissed updates:', err as Error);
|
||||
logError('Failed to load dismissed updates:', err as Error);
|
||||
setDismissedUpdatesLoaded(true);
|
||||
}
|
||||
};
|
||||
|
|
@ -51,7 +51,7 @@ export const useUpdateChecker = () => {
|
|||
Array.from(updates)
|
||||
);
|
||||
} 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) {
|
||||
error('Failed to check for updates:', err as Error);
|
||||
logError('Failed to check for updates:', err as Error);
|
||||
} finally {
|
||||
setIsChecking(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,11 @@ import {
|
|||
detectBackendSupport,
|
||||
getAvailableBackends,
|
||||
} from '@/main/modules/binary';
|
||||
import {
|
||||
setMainWindow,
|
||||
startMonitoring,
|
||||
stopMonitoring,
|
||||
} from '@/main/modules/monitoring';
|
||||
import type { FrontendPreference } from '@/types';
|
||||
|
||||
async function launchKoboldCppWithCustomFrontends(args: string[] = []) {
|
||||
|
|
@ -229,4 +234,16 @@ export function setupIPCHandlers() {
|
|||
);
|
||||
|
||||
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 */
|
||||
import si from 'systeminformation';
|
||||
import { shortenDeviceName } from '@/utils/hardware';
|
||||
import { logError } from '@/main/modules/logging';
|
||||
import { terminateProcess } from '@/utils/process';
|
||||
import type {
|
||||
|
|
@ -8,8 +7,10 @@ import type {
|
|||
GPUCapabilities,
|
||||
BasicGPUInfo,
|
||||
GPUMemoryInfo,
|
||||
HardwareDetectionResult,
|
||||
} from '@/types/hardware';
|
||||
import { spawn } from 'child_process';
|
||||
import { formatDeviceName } from '@/utils/format';
|
||||
|
||||
let cpuCapabilitiesCache: CPUCapabilities | null = null;
|
||||
let basicGPUInfoCache: BasicGPUInfo | null = null;
|
||||
|
|
@ -26,7 +27,7 @@ export async function detectCPU() {
|
|||
|
||||
const devices: string[] = [];
|
||||
if (cpu.brand) {
|
||||
devices.push(shortenDeviceName(cpu.brand));
|
||||
devices.push(formatDeviceName(cpu.brand));
|
||||
}
|
||||
|
||||
const avx = flags.includes('avx') || flags.includes('AVX');
|
||||
|
|
@ -65,7 +66,7 @@ export async function detectGPU() {
|
|||
|
||||
for (const controller of graphics.controllers) {
|
||||
if (controller.model) {
|
||||
gpuInfo.push(shortenDeviceName(controller.model));
|
||||
gpuInfo.push(formatDeviceName(controller.model));
|
||||
}
|
||||
|
||||
const vendor = controller.vendor?.toLowerCase() || '';
|
||||
|
|
@ -142,10 +143,7 @@ async function detectCUDA() {
|
|||
output += data.toString();
|
||||
});
|
||||
|
||||
return new Promise<{
|
||||
supported: boolean;
|
||||
devices: string[];
|
||||
}>((resolve) => {
|
||||
return new Promise<HardwareDetectionResult>((resolve) => {
|
||||
nvidia.on('close', (code) => {
|
||||
if (code === 0 && output.trim()) {
|
||||
const devices = output
|
||||
|
|
@ -154,7 +152,7 @@ async function detectCUDA() {
|
|||
.map((line) => {
|
||||
const parts = line.split(',');
|
||||
const rawName = parts[0]?.trim() || 'Unknown NVIDIA GPU';
|
||||
return shortenDeviceName(rawName);
|
||||
return formatDeviceName(rawName);
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
|
|
@ -206,10 +204,7 @@ export async function detectROCm() {
|
|||
output += data.toString();
|
||||
});
|
||||
|
||||
return new Promise<{
|
||||
supported: boolean;
|
||||
devices: string[];
|
||||
}>((resolve) => {
|
||||
return new Promise<HardwareDetectionResult>((resolve) => {
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
rocminfo.on('close', (code) => {
|
||||
if (code === 0 && output.trim()) {
|
||||
|
|
@ -225,9 +220,9 @@ export async function detectROCm() {
|
|||
if (
|
||||
name &&
|
||||
!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') {
|
||||
devices.push(shortenDeviceName(name));
|
||||
devices.push(formatDeviceName(name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -301,10 +296,7 @@ async function detectVulkan() {
|
|||
output += data.toString();
|
||||
});
|
||||
|
||||
return new Promise<{
|
||||
supported: boolean;
|
||||
devices: string[];
|
||||
}>((resolve) => {
|
||||
return new Promise<HardwareDetectionResult>((resolve) => {
|
||||
vulkaninfo.on('close', (code) => {
|
||||
if (code === 0 && output.trim()) {
|
||||
const devices: string[] = [];
|
||||
|
|
@ -316,7 +308,7 @@ async function detectVulkan() {
|
|||
if (parts.length >= 2) {
|
||||
const name = parts[1]?.trim();
|
||||
if (name) {
|
||||
devices.push(shortenDeviceName(name));
|
||||
devices.push(formatDeviceName(name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -363,7 +355,7 @@ function parseClInfoOutput(output: string) {
|
|||
const deviceName = findDeviceNameInClInfo(lines, i);
|
||||
|
||||
if (deviceName && currentPlatform) {
|
||||
const deviceLabel = `${shortenDeviceName(deviceName)} (${currentPlatform})`;
|
||||
const deviceLabel = `${formatDeviceName(deviceName)} (${currentPlatform})`;
|
||||
devices.push(deviceLabel);
|
||||
}
|
||||
}
|
||||
|
|
@ -407,10 +399,7 @@ async function detectCLBlast() {
|
|||
output += data.toString();
|
||||
});
|
||||
|
||||
return new Promise<{
|
||||
supported: boolean;
|
||||
devices: string[];
|
||||
}>((resolve) => {
|
||||
return new Promise<HardwareDetectionResult>((resolve) => {
|
||||
clinfo.on('close', (code) => {
|
||||
if (code === 0 && output.trim()) {
|
||||
const devices = parseClInfoOutput(output);
|
||||
|
|
@ -456,7 +445,7 @@ export async function detectGPUMemory() {
|
|||
}
|
||||
|
||||
memoryInfo.push({
|
||||
deviceName: shortenDeviceName(controller.model),
|
||||
deviceName: formatDeviceName(controller.model),
|
||||
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');
|
||||
|
||||
mainWindow = new BrowserWindow({
|
||||
minWidth: 600,
|
||||
minHeight: 600,
|
||||
width: windowWidth,
|
||||
height: windowHeight,
|
||||
x: Math.floor((size.width - windowWidth) / 2),
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import type {
|
|||
LogsAPI,
|
||||
SillyTavernAPI,
|
||||
OpenWebUIAPI,
|
||||
MonitoringAPI,
|
||||
} from '@/types/electron';
|
||||
|
||||
const koboldAPI: KoboldAPI = {
|
||||
|
|
@ -107,6 +108,17 @@ const openwebUIAPI: OpenWebUIAPI = {
|
|||
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', {
|
||||
kobold: koboldAPI,
|
||||
app: appAPI,
|
||||
|
|
@ -114,4 +126,5 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||
logs: logsAPI,
|
||||
sillytavern: sillyTavernAPI,
|
||||
openwebui: openwebUIAPI,
|
||||
monitoring: monitoringAPI,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -375,7 +375,7 @@ export const useLaunchConfigStore = create<LaunchConfigState>((set, get) => ({
|
|||
loadConfigFromFile: async (
|
||||
configFiles: ConfigFile[],
|
||||
savedConfig: string | null
|
||||
): Promise<string | null> => {
|
||||
) => {
|
||||
let currentSelectedFile = null;
|
||||
|
||||
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';
|
||||
import type { BackendOption, BackendSupport } from '@/types';
|
||||
import type { MantineColorScheme } from '@mantine/core';
|
||||
import type { SystemMetrics } from '@/main/modules/monitoring';
|
||||
|
||||
export interface GitHubAsset {
|
||||
name: string;
|
||||
|
|
@ -172,6 +173,13 @@ export interface OpenWebUIAPI {
|
|||
isUvAvailable: () => Promise<boolean>;
|
||||
}
|
||||
|
||||
export interface MonitoringAPI {
|
||||
start: () => Promise<void>;
|
||||
stop: () => Promise<void>;
|
||||
onMetrics: (callback: (metrics: SystemMetrics) => void) => void;
|
||||
removeMetricsListener: () => void;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
electronAPI: {
|
||||
|
|
@ -181,6 +189,7 @@ declare global {
|
|||
logs: LogsAPI;
|
||||
sillytavern: SillyTavernAPI;
|
||||
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[];
|
||||
}
|
||||
|
||||
export interface HardwareDetectionResult {
|
||||
supported: boolean;
|
||||
devices: string[];
|
||||
}
|
||||
|
||||
export interface HardwareInfo {
|
||||
cpu: CPUCapabilities;
|
||||
gpu: BasicGPUInfo;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { ASSET_SUFFIXES } from '@/constants';
|
||||
import { stripAssetExtensions } from '@/utils/version';
|
||||
|
||||
export const getAssetDescription = (assetName: string): string => {
|
||||
export const getAssetDescription = (assetName: string) => {
|
||||
const name = stripAssetExtensions(assetName).toLowerCase();
|
||||
|
||||
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.";
|
||||
};
|
||||
|
||||
export const isAssetStandard = (assetName: string): boolean => {
|
||||
export const isAssetStandard = (assetName: string) => {
|
||||
const name = stripAssetExtensions(assetName).toLowerCase();
|
||||
|
||||
return (
|
||||
|
|
@ -54,7 +54,7 @@ export const sortDownloadsByType = <T extends { name: string }>(
|
|||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
|
||||
export const pretifyBinName = (binName: string): string => {
|
||||
export const pretifyBinName = (binName: string) => {
|
||||
const cleanName = stripAssetExtensions(binName).toLowerCase();
|
||||
|
||||
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 { constants } from 'fs';
|
||||
|
||||
export const pathExists = async (path: string): Promise<boolean> => {
|
||||
export const pathExists = async (path: string) => {
|
||||
try {
|
||||
await access(path, constants.F_OK);
|
||||
return true;
|
||||
|
|
@ -21,15 +21,12 @@ export const readJsonFile = async <T = unknown>(
|
|||
}
|
||||
};
|
||||
|
||||
export const writeJsonFile = async (
|
||||
path: string,
|
||||
data: unknown
|
||||
): Promise<void> => {
|
||||
export const writeJsonFile = async (path: string, data: unknown) => {
|
||||
const content = JSON.stringify(data, null, 2);
|
||||
await writeFile(path, content, 'utf-8');
|
||||
};
|
||||
|
||||
export const ensureDir = async (path: string): Promise<void> => {
|
||||
export const ensureDir = async (path: string) => {
|
||||
try {
|
||||
await mkdir(path, { recursive: true });
|
||||
} 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 {
|
||||
host: string;
|
||||
port: number;
|
||||
isImageMode: boolean;
|
||||
}
|
||||
|
||||
export function parseKoboldConfig(args: string[]): KoboldConfig {
|
||||
export function parseKoboldConfig(args: string[]) {
|
||||
let host = 'localhost';
|
||||
let port = 5001;
|
||||
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) {
|
||||
window.electronAPI.logs.logError(message, error);
|
||||
}
|
||||
|
|
@ -19,7 +19,7 @@ export const safeExecute = async <T>(
|
|||
export const tryExecute = async (
|
||||
operation: () => Promise<void>,
|
||||
errorMessage: string
|
||||
): Promise<boolean> => {
|
||||
) => {
|
||||
try {
|
||||
await operation();
|
||||
return true;
|
||||
|
|
@ -28,7 +28,3 @@ export const tryExecute = async (
|
|||
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 { PRODUCT_NAME, CONFIG_FILE_NAME } from '@/constants';
|
||||
|
||||
export function getConfigDir(): string {
|
||||
export function getConfigDir() {
|
||||
return join(getConfigDirPath(), CONFIG_FILE_NAME);
|
||||
}
|
||||
|
||||
function getConfigDirPath(): string {
|
||||
function getConfigDirPath() {
|
||||
const platform = process.platform;
|
||||
const home = homedir();
|
||||
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ export interface ProcessTerminationOptions {
|
|||
async function killWindowsProcessTree(
|
||||
pid: number,
|
||||
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'], {
|
||||
stdio: 'pipe',
|
||||
});
|
||||
|
|
@ -35,7 +35,7 @@ async function killWindowsProcessTree(
|
|||
export async function terminateProcess(
|
||||
childProcess: ChildProcess,
|
||||
options: ProcessTerminationOptions = {}
|
||||
): Promise<void> {
|
||||
) {
|
||||
const { timeoutMs = 3000, logError } = options;
|
||||
|
||||
if (!childProcess?.pid) {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import { GITHUB_API, ROCM } from '@/constants';
|
||||
import type { DownloadItem } from '@/types/electron';
|
||||
import type { GitHubAsset } from '@/types';
|
||||
|
||||
export async function getROCmDownload(): Promise<DownloadItem | null> {
|
||||
export async function getROCmDownload() {
|
||||
const platform = await window.electronAPI.kobold.getPlatform();
|
||||
|
||||
if (platform === 'linux') {
|
||||
|
|
@ -14,7 +13,7 @@ export async function getROCmDownload(): Promise<DownloadItem | null> {
|
|||
return null;
|
||||
}
|
||||
|
||||
async function getLinuxROCmDownload(): Promise<DownloadItem | null> {
|
||||
async function getLinuxROCmDownload() {
|
||||
try {
|
||||
const response = await fetch(GITHUB_API.LATEST_RELEASE_URL);
|
||||
if (!response.ok) {
|
||||
|
|
@ -40,7 +39,7 @@ async function getLinuxROCmDownload(): Promise<DownloadItem | null> {
|
|||
}
|
||||
}
|
||||
|
||||
async function getWindowsROCmDownload(): Promise<DownloadItem | null> {
|
||||
async function getWindowsROCmDownload() {
|
||||
try {
|
||||
const response = await fetch(GITHUB_API.ROCM_LATEST_RELEASE_URL);
|
||||
if (!response.ok) {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
import { error } from '@/utils/logger';
|
||||
import { logError } from '@/utils/logger';
|
||||
|
||||
export const handleTerminalOutput = (
|
||||
prevContent: string,
|
||||
newData: string
|
||||
): string => {
|
||||
export const handleTerminalOutput = (prevContent: string, newData: string) => {
|
||||
try {
|
||||
if (newData.includes('\r')) {
|
||||
const hasStandaloneCarriageReturns = /\r(?!\n)/g.test(newData);
|
||||
|
|
@ -32,14 +29,14 @@ export const handleTerminalOutput = (
|
|||
|
||||
return prevContent + newData;
|
||||
} catch (err) {
|
||||
error('Terminal Basic Error', err as Error);
|
||||
logError('Terminal Basic Error', err as Error);
|
||||
return prevContent + newData;
|
||||
}
|
||||
};
|
||||
|
||||
const URL_REGEX = /(https?:\/\/[^\s<>"{}|\\^`[\]]+)/gi;
|
||||
|
||||
const linkifyText = (text: string): string =>
|
||||
const linkifyText = (text: string) =>
|
||||
text.replace(URL_REGEX, (url) => {
|
||||
const cleanUrl = url.replace(/[.,;:!?]+$/, '');
|
||||
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}`;
|
||||
});
|
||||
|
||||
const escapeHtmlExceptLinks = (text: string): string => {
|
||||
const escapeHtmlExceptLinks = (text: string) => {
|
||||
const escaped = text
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
|
|
@ -58,7 +55,7 @@ const escapeHtmlExceptLinks = (text: string): string => {
|
|||
return linkifyText(escaped);
|
||||
};
|
||||
|
||||
export const processTerminalContent = (content: string): string => {
|
||||
export const processTerminalContent = (content: string) => {
|
||||
if (!content) return '';
|
||||
|
||||
return escapeHtmlExceptLinks(content);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
const isValidUrl = (string: string): boolean => {
|
||||
const isValidUrl = (string: string) => {
|
||||
try {
|
||||
const url = new URL(string);
|
||||
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;
|
||||
|
||||
const validExtensions = ['.gguf'];
|
||||
|
|
@ -18,9 +18,7 @@ const isValidFilePath = (path: string): boolean => {
|
|||
return hasValidExtension || path.includes('/') || path.includes('\\');
|
||||
};
|
||||
|
||||
export const getInputValidationState = (
|
||||
path: string
|
||||
): 'valid' | 'invalid' | 'neutral' => {
|
||||
export const getInputValidationState = (path: string) => {
|
||||
if (!path.trim()) return 'neutral';
|
||||
|
||||
if (isValidUrl(path) || isValidFilePath(path)) {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
import type { InstalledVersion } from '@/types';
|
||||
|
||||
export const getDisplayNameFromPath = (
|
||||
installedVersion: InstalledVersion
|
||||
): string => {
|
||||
export const getDisplayNameFromPath = (installedVersion: InstalledVersion) => {
|
||||
const pathParts = installedVersion.path.split(/[/\\]/);
|
||||
const launcherIndex = pathParts.findIndex(
|
||||
(part) => part === 'koboldcpp-launcher' || part === 'koboldcpp-launcher.exe'
|
||||
|
|
@ -15,16 +13,16 @@ export const getDisplayNameFromPath = (
|
|||
return installedVersion.filename;
|
||||
};
|
||||
|
||||
export const stripAssetExtensions = (assetName: string): string =>
|
||||
export const stripAssetExtensions = (assetName: string) =>
|
||||
assetName.replace(/\.(tar\.gz|zip|exe|dmg|AppImage)$/i, '');
|
||||
|
||||
const stripVersionSuffix = (displayName: string): string =>
|
||||
const stripVersionSuffix = (displayName: string) =>
|
||||
displayName.replace(
|
||||
/-(\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 =>
|
||||
version.replace(/^v/, '').replace(/[^0-9.]/g, '');
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { ZOOM } from '@/constants';
|
||||
|
||||
export const zoomLevelToPercentage = (zoomLevel: number): number =>
|
||||
export const zoomLevelToPercentage = (zoomLevel: number) =>
|
||||
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);
|
||||
|
||||
export const isValidZoomPercentage = (percentage: number): boolean =>
|
||||
export const isValidZoomPercentage = (percentage: number) =>
|
||||
percentage >= ZOOM.MIN_PERCENTAGE && percentage <= ZOOM.MAX_PERCENTAGE;
|
||||
|
|
|
|||
232
yarn.lock
232
yarn.lock
|
|
@ -585,13 +585,13 @@ __metadata:
|
|||
linkType: hard
|
||||
|
||||
"@eslint-community/eslint-utils@npm:^4.7.0, @eslint-community/eslint-utils@npm:^4.8.0":
|
||||
version: 4.8.0
|
||||
resolution: "@eslint-community/eslint-utils@npm:4.8.0"
|
||||
version: 4.9.0
|
||||
resolution: "@eslint-community/eslint-utils@npm:4.9.0"
|
||||
dependencies:
|
||||
eslint-visitor-keys: "npm:^3.4.3"
|
||||
peerDependencies:
|
||||
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
|
||||
checksum: 10c0/33b93d2a4e9d5fe4c11d02d0fc5ed69e12fcb1e7ca031ded0d6adb24e768c36df77288ed79eecc784f9db34219816247db27688dfe869fb7fbf096840a097d7a
|
||||
checksum: 10c0/8881e22d519326e7dba85ea915ac7a143367c805e6ba1374c987aa2fbdd09195cc51183d2da72c0e2ff388f84363e1b220fd0d19bef10c272c63455162176817
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
@ -864,9 +864,9 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@mantine/core@npm:^8.3.0":
|
||||
version: 8.3.0
|
||||
resolution: "@mantine/core@npm:8.3.0"
|
||||
"@mantine/core@npm:^8.3.1":
|
||||
version: 8.3.1
|
||||
resolution: "@mantine/core@npm:8.3.1"
|
||||
dependencies:
|
||||
"@floating-ui/react": "npm:^0.27.16"
|
||||
clsx: "npm:^2.1.1"
|
||||
|
|
@ -875,19 +875,19 @@ __metadata:
|
|||
react-textarea-autosize: "npm:8.5.9"
|
||||
type-fest: "npm:^4.41.0"
|
||||
peerDependencies:
|
||||
"@mantine/hooks": 8.3.0
|
||||
"@mantine/hooks": 8.3.1
|
||||
react: ^18.x || ^19.x
|
||||
react-dom: ^18.x || ^19.x
|
||||
checksum: 10c0/28430c11c726af9e5b992bc195614cc67a8d1a4d25f3ced6753ab0399d67c6bb927c1b5eb4f383f8f75ef09eec3b17998761ab5983c850459d7cf34e75d0688b
|
||||
checksum: 10c0/6faab4553d35e3f676e852ea9b926e0d3b744eda5b38d450eb3bdb89c92cdd2ce8ae9fd7e4b47c83fc870d215a62c8fa3299d25fcaa83a07f67c782883a8ca30
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@mantine/hooks@npm:^8.3.0":
|
||||
version: 8.3.0
|
||||
resolution: "@mantine/hooks@npm:8.3.0"
|
||||
"@mantine/hooks@npm:^8.3.1":
|
||||
version: 8.3.1
|
||||
resolution: "@mantine/hooks@npm:8.3.1"
|
||||
peerDependencies:
|
||||
react: ^18.x || ^19.x
|
||||
checksum: 10c0/2caaac7654a97424e798d3ae18ee9c70219a62d69dbd21582da90f44b8381674ed496583e4e7ad378a2fd42b7a78fb84534982297918acb24ed674abcb1a312b
|
||||
checksum: 10c0/d64ea9c848a687668ce34007e3ab226b5c45c52ea1f4bdd721ca17b07d7f7a642b86e23ee65777179d6cd1ae9c3ce56fe43602828f332bfc2cbd59658b1c9047
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
@ -974,149 +974,149 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rollup/rollup-android-arm-eabi@npm:4.50.0":
|
||||
version: 4.50.0
|
||||
resolution: "@rollup/rollup-android-arm-eabi@npm:4.50.0"
|
||||
"@rollup/rollup-android-arm-eabi@npm:4.50.1":
|
||||
version: 4.50.1
|
||||
resolution: "@rollup/rollup-android-arm-eabi@npm:4.50.1"
|
||||
conditions: os=android & cpu=arm
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rollup/rollup-android-arm64@npm:4.50.0":
|
||||
version: 4.50.0
|
||||
resolution: "@rollup/rollup-android-arm64@npm:4.50.0"
|
||||
"@rollup/rollup-android-arm64@npm:4.50.1":
|
||||
version: 4.50.1
|
||||
resolution: "@rollup/rollup-android-arm64@npm:4.50.1"
|
||||
conditions: os=android & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rollup/rollup-darwin-arm64@npm:4.50.0":
|
||||
version: 4.50.0
|
||||
resolution: "@rollup/rollup-darwin-arm64@npm:4.50.0"
|
||||
"@rollup/rollup-darwin-arm64@npm:4.50.1":
|
||||
version: 4.50.1
|
||||
resolution: "@rollup/rollup-darwin-arm64@npm:4.50.1"
|
||||
conditions: os=darwin & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rollup/rollup-darwin-x64@npm:4.50.0":
|
||||
version: 4.50.0
|
||||
resolution: "@rollup/rollup-darwin-x64@npm:4.50.0"
|
||||
"@rollup/rollup-darwin-x64@npm:4.50.1":
|
||||
version: 4.50.1
|
||||
resolution: "@rollup/rollup-darwin-x64@npm:4.50.1"
|
||||
conditions: os=darwin & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rollup/rollup-freebsd-arm64@npm:4.50.0":
|
||||
version: 4.50.0
|
||||
resolution: "@rollup/rollup-freebsd-arm64@npm:4.50.0"
|
||||
"@rollup/rollup-freebsd-arm64@npm:4.50.1":
|
||||
version: 4.50.1
|
||||
resolution: "@rollup/rollup-freebsd-arm64@npm:4.50.1"
|
||||
conditions: os=freebsd & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rollup/rollup-freebsd-x64@npm:4.50.0":
|
||||
version: 4.50.0
|
||||
resolution: "@rollup/rollup-freebsd-x64@npm:4.50.0"
|
||||
"@rollup/rollup-freebsd-x64@npm:4.50.1":
|
||||
version: 4.50.1
|
||||
resolution: "@rollup/rollup-freebsd-x64@npm:4.50.1"
|
||||
conditions: os=freebsd & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rollup/rollup-linux-arm-gnueabihf@npm:4.50.0":
|
||||
version: 4.50.0
|
||||
resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.50.0"
|
||||
"@rollup/rollup-linux-arm-gnueabihf@npm:4.50.1":
|
||||
version: 4.50.1
|
||||
resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.50.1"
|
||||
conditions: os=linux & cpu=arm & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rollup/rollup-linux-arm-musleabihf@npm:4.50.0":
|
||||
version: 4.50.0
|
||||
resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.50.0"
|
||||
"@rollup/rollup-linux-arm-musleabihf@npm:4.50.1":
|
||||
version: 4.50.1
|
||||
resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.50.1"
|
||||
conditions: os=linux & cpu=arm & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rollup/rollup-linux-arm64-gnu@npm:4.50.0":
|
||||
version: 4.50.0
|
||||
resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.50.0"
|
||||
"@rollup/rollup-linux-arm64-gnu@npm:4.50.1":
|
||||
version: 4.50.1
|
||||
resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.50.1"
|
||||
conditions: os=linux & cpu=arm64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rollup/rollup-linux-arm64-musl@npm:4.50.0":
|
||||
version: 4.50.0
|
||||
resolution: "@rollup/rollup-linux-arm64-musl@npm:4.50.0"
|
||||
"@rollup/rollup-linux-arm64-musl@npm:4.50.1":
|
||||
version: 4.50.1
|
||||
resolution: "@rollup/rollup-linux-arm64-musl@npm:4.50.1"
|
||||
conditions: os=linux & cpu=arm64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rollup/rollup-linux-loongarch64-gnu@npm:4.50.0":
|
||||
version: 4.50.0
|
||||
resolution: "@rollup/rollup-linux-loongarch64-gnu@npm:4.50.0"
|
||||
"@rollup/rollup-linux-loongarch64-gnu@npm:4.50.1":
|
||||
version: 4.50.1
|
||||
resolution: "@rollup/rollup-linux-loongarch64-gnu@npm:4.50.1"
|
||||
conditions: os=linux & cpu=loong64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rollup/rollup-linux-ppc64-gnu@npm:4.50.0":
|
||||
version: 4.50.0
|
||||
resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.50.0"
|
||||
"@rollup/rollup-linux-ppc64-gnu@npm:4.50.1":
|
||||
version: 4.50.1
|
||||
resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.50.1"
|
||||
conditions: os=linux & cpu=ppc64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rollup/rollup-linux-riscv64-gnu@npm:4.50.0":
|
||||
version: 4.50.0
|
||||
resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.50.0"
|
||||
"@rollup/rollup-linux-riscv64-gnu@npm:4.50.1":
|
||||
version: 4.50.1
|
||||
resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.50.1"
|
||||
conditions: os=linux & cpu=riscv64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rollup/rollup-linux-riscv64-musl@npm:4.50.0":
|
||||
version: 4.50.0
|
||||
resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.50.0"
|
||||
"@rollup/rollup-linux-riscv64-musl@npm:4.50.1":
|
||||
version: 4.50.1
|
||||
resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.50.1"
|
||||
conditions: os=linux & cpu=riscv64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rollup/rollup-linux-s390x-gnu@npm:4.50.0":
|
||||
version: 4.50.0
|
||||
resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.50.0"
|
||||
"@rollup/rollup-linux-s390x-gnu@npm:4.50.1":
|
||||
version: 4.50.1
|
||||
resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.50.1"
|
||||
conditions: os=linux & cpu=s390x & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rollup/rollup-linux-x64-gnu@npm:4.50.0":
|
||||
version: 4.50.0
|
||||
resolution: "@rollup/rollup-linux-x64-gnu@npm:4.50.0"
|
||||
"@rollup/rollup-linux-x64-gnu@npm:4.50.1":
|
||||
version: 4.50.1
|
||||
resolution: "@rollup/rollup-linux-x64-gnu@npm:4.50.1"
|
||||
conditions: os=linux & cpu=x64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rollup/rollup-linux-x64-musl@npm:4.50.0":
|
||||
version: 4.50.0
|
||||
resolution: "@rollup/rollup-linux-x64-musl@npm:4.50.0"
|
||||
"@rollup/rollup-linux-x64-musl@npm:4.50.1":
|
||||
version: 4.50.1
|
||||
resolution: "@rollup/rollup-linux-x64-musl@npm:4.50.1"
|
||||
conditions: os=linux & cpu=x64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rollup/rollup-openharmony-arm64@npm:4.50.0":
|
||||
version: 4.50.0
|
||||
resolution: "@rollup/rollup-openharmony-arm64@npm:4.50.0"
|
||||
"@rollup/rollup-openharmony-arm64@npm:4.50.1":
|
||||
version: 4.50.1
|
||||
resolution: "@rollup/rollup-openharmony-arm64@npm:4.50.1"
|
||||
conditions: os=openharmony & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rollup/rollup-win32-arm64-msvc@npm:4.50.0":
|
||||
version: 4.50.0
|
||||
resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.50.0"
|
||||
"@rollup/rollup-win32-arm64-msvc@npm:4.50.1":
|
||||
version: 4.50.1
|
||||
resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.50.1"
|
||||
conditions: os=win32 & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rollup/rollup-win32-ia32-msvc@npm:4.50.0":
|
||||
version: 4.50.0
|
||||
resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.50.0"
|
||||
"@rollup/rollup-win32-ia32-msvc@npm:4.50.1":
|
||||
version: 4.50.1
|
||||
resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.50.1"
|
||||
conditions: os=win32 & cpu=ia32
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rollup/rollup-win32-x64-msvc@npm:4.50.0":
|
||||
version: 4.50.0
|
||||
resolution: "@rollup/rollup-win32-x64-msvc@npm:4.50.0"
|
||||
"@rollup/rollup-win32-x64-msvc@npm:4.50.1":
|
||||
version: 4.50.1
|
||||
resolution: "@rollup/rollup-win32-x64-msvc@npm:4.50.1"
|
||||
conditions: os=win32 & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
|
@ -1614,9 +1614,9 @@ __metadata:
|
|||
linkType: hard
|
||||
|
||||
"ansi-regex@npm:^6.0.1":
|
||||
version: 6.2.0
|
||||
resolution: "ansi-regex@npm:6.2.0"
|
||||
checksum: 10c0/20a2e55ae9816074a60e6729dbe3daad664cd967fc82acc08b02f5677db84baa688babf940d71f50acbbb184c02459453789705e079f4d521166ae66451de551
|
||||
version: 6.2.2
|
||||
resolution: "ansi-regex@npm:6.2.2"
|
||||
checksum: 10c0/05d4acb1d2f59ab2cf4b794339c7b168890d44dda4bf0ce01152a8da0213aca207802f930442ce8cd22d7a92f44907664aac6508904e75e038fa944d2601b30f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
@ -1630,9 +1630,9 @@ __metadata:
|
|||
linkType: hard
|
||||
|
||||
"ansi-styles@npm:^6.1.0":
|
||||
version: 6.2.1
|
||||
resolution: "ansi-styles@npm:6.2.1"
|
||||
checksum: 10c0/5d1ec38c123984bcedd996eac680d548f31828bd679a66db2bdf11844634dde55fec3efa9c6bb1d89056a5e79c1ac540c4c784d592ea1d25028a92227d2f2d5c
|
||||
version: 6.2.3
|
||||
resolution: "ansi-styles@npm:6.2.3"
|
||||
checksum: 10c0/23b8a4ce14e18fb854693b95351e286b771d23d8844057ed2e7d083cd3e708376c3323707ec6a24365f7d7eda3ca00327fe04092e29e551499ec4c8b7bfac868
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
@ -2670,9 +2670,9 @@ __metadata:
|
|||
linkType: hard
|
||||
|
||||
"electron-to-chromium@npm:^1.5.211":
|
||||
version: 1.5.214
|
||||
resolution: "electron-to-chromium@npm:1.5.214"
|
||||
checksum: 10c0/76ca22fd97a2dad84a710915b5984263b31e61c7883cd3ec0c11c0d7beb3fa628780cdfd05a96ec79a904ea1c910cf02c513db60f31b627c96743e50f6b11a2e
|
||||
version: 1.5.215
|
||||
resolution: "electron-to-chromium@npm:1.5.215"
|
||||
checksum: 10c0/3a45976d1193e57284533096b3bbec218a5d4d85af4f7c133522aae35b14bbf22734f48ccc3f0e43a451441ebc375fa2f4350390fd729dcedb97543692133e39
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
@ -3627,8 +3627,8 @@ __metadata:
|
|||
resolution: "gerbil@workspace:."
|
||||
dependencies:
|
||||
"@eslint/js": "npm:^9.35.0"
|
||||
"@mantine/core": "npm:^8.3.0"
|
||||
"@mantine/hooks": "npm:^8.3.0"
|
||||
"@mantine/core": "npm:^8.3.1"
|
||||
"@mantine/hooks": "npm:^8.3.1"
|
||||
"@types/node": "npm:^24.3.1"
|
||||
"@types/react": "npm:^19.1.12"
|
||||
"@types/react-dom": "npm:^19.1.9"
|
||||
|
|
@ -4777,11 +4777,11 @@ __metadata:
|
|||
linkType: hard
|
||||
|
||||
"magic-string@npm:^0.30.17":
|
||||
version: 0.30.18
|
||||
resolution: "magic-string@npm:0.30.18"
|
||||
version: 0.30.19
|
||||
resolution: "magic-string@npm:0.30.19"
|
||||
dependencies:
|
||||
"@jridgewell/sourcemap-codec": "npm:^1.5.5"
|
||||
checksum: 10c0/80fba01e13ce1f5c474a0498a5aa462fa158eb56567310747089a0033e432d83a2021ee2c109ac116010cd9dcf90a5231d89fbe3858165f73c00a50a74dbefcd
|
||||
checksum: 10c0/db23fd2e2ee98a1aeb88a4cdb2353137fcf05819b883c856dd79e4c7dfb25151e2a5a4d5dbd88add5e30ed8ae5c51bcf4accbc6becb75249d924ec7b4fbcae27
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
@ -6027,30 +6027,30 @@ __metadata:
|
|||
linkType: hard
|
||||
|
||||
"rollup@npm:^4.43.0":
|
||||
version: 4.50.0
|
||||
resolution: "rollup@npm:4.50.0"
|
||||
version: 4.50.1
|
||||
resolution: "rollup@npm:4.50.1"
|
||||
dependencies:
|
||||
"@rollup/rollup-android-arm-eabi": "npm:4.50.0"
|
||||
"@rollup/rollup-android-arm64": "npm:4.50.0"
|
||||
"@rollup/rollup-darwin-arm64": "npm:4.50.0"
|
||||
"@rollup/rollup-darwin-x64": "npm:4.50.0"
|
||||
"@rollup/rollup-freebsd-arm64": "npm:4.50.0"
|
||||
"@rollup/rollup-freebsd-x64": "npm:4.50.0"
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "npm:4.50.0"
|
||||
"@rollup/rollup-linux-arm-musleabihf": "npm:4.50.0"
|
||||
"@rollup/rollup-linux-arm64-gnu": "npm:4.50.0"
|
||||
"@rollup/rollup-linux-arm64-musl": "npm:4.50.0"
|
||||
"@rollup/rollup-linux-loongarch64-gnu": "npm:4.50.0"
|
||||
"@rollup/rollup-linux-ppc64-gnu": "npm:4.50.0"
|
||||
"@rollup/rollup-linux-riscv64-gnu": "npm:4.50.0"
|
||||
"@rollup/rollup-linux-riscv64-musl": "npm:4.50.0"
|
||||
"@rollup/rollup-linux-s390x-gnu": "npm:4.50.0"
|
||||
"@rollup/rollup-linux-x64-gnu": "npm:4.50.0"
|
||||
"@rollup/rollup-linux-x64-musl": "npm:4.50.0"
|
||||
"@rollup/rollup-openharmony-arm64": "npm:4.50.0"
|
||||
"@rollup/rollup-win32-arm64-msvc": "npm:4.50.0"
|
||||
"@rollup/rollup-win32-ia32-msvc": "npm:4.50.0"
|
||||
"@rollup/rollup-win32-x64-msvc": "npm:4.50.0"
|
||||
"@rollup/rollup-android-arm-eabi": "npm:4.50.1"
|
||||
"@rollup/rollup-android-arm64": "npm:4.50.1"
|
||||
"@rollup/rollup-darwin-arm64": "npm:4.50.1"
|
||||
"@rollup/rollup-darwin-x64": "npm:4.50.1"
|
||||
"@rollup/rollup-freebsd-arm64": "npm:4.50.1"
|
||||
"@rollup/rollup-freebsd-x64": "npm:4.50.1"
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "npm:4.50.1"
|
||||
"@rollup/rollup-linux-arm-musleabihf": "npm:4.50.1"
|
||||
"@rollup/rollup-linux-arm64-gnu": "npm:4.50.1"
|
||||
"@rollup/rollup-linux-arm64-musl": "npm:4.50.1"
|
||||
"@rollup/rollup-linux-loongarch64-gnu": "npm:4.50.1"
|
||||
"@rollup/rollup-linux-ppc64-gnu": "npm:4.50.1"
|
||||
"@rollup/rollup-linux-riscv64-gnu": "npm:4.50.1"
|
||||
"@rollup/rollup-linux-riscv64-musl": "npm:4.50.1"
|
||||
"@rollup/rollup-linux-s390x-gnu": "npm:4.50.1"
|
||||
"@rollup/rollup-linux-x64-gnu": "npm:4.50.1"
|
||||
"@rollup/rollup-linux-x64-musl": "npm:4.50.1"
|
||||
"@rollup/rollup-openharmony-arm64": "npm:4.50.1"
|
||||
"@rollup/rollup-win32-arm64-msvc": "npm:4.50.1"
|
||||
"@rollup/rollup-win32-ia32-msvc": "npm:4.50.1"
|
||||
"@rollup/rollup-win32-x64-msvc": "npm:4.50.1"
|
||||
"@types/estree": "npm:1.0.8"
|
||||
fsevents: "npm:~2.3.2"
|
||||
dependenciesMeta:
|
||||
|
|
@ -6100,7 +6100,7 @@ __metadata:
|
|||
optional: true
|
||||
bin:
|
||||
rollup: dist/bin/rollup
|
||||
checksum: 10c0/8a9aa0885b61ee08315cdc097fb9f97b626a0992546a6857d7668f4690a7344d8497fa7504e4dba03acefc77f03d90fec3c11bfa80777177e76ea5d8c18ddc97
|
||||
checksum: 10c0/2029282826d5fb4e308be261b2c28329a4d2bd34304cc3960da69fd21d5acccd0267d6770b1656ffc8f166203ef7e865b4583d5f842a519c8ef059ac71854205
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
@ -6618,11 +6618,11 @@ __metadata:
|
|||
linkType: hard
|
||||
|
||||
"strip-ansi@npm:^7.0.1":
|
||||
version: 7.1.0
|
||||
resolution: "strip-ansi@npm:7.1.0"
|
||||
version: 7.1.2
|
||||
resolution: "strip-ansi@npm:7.1.2"
|
||||
dependencies:
|
||||
ansi-regex: "npm:^6.0.1"
|
||||
checksum: 10c0/a198c3762e8832505328cbf9e8c8381de14a4fa50a4f9b2160138158ea88c0f5549fb50cb13c651c3088f47e63a108b34622ec18c0499b6c8c3a5ddf6b305ac4
|
||||
checksum: 10c0/0d6d7a023de33368fd042aab0bf48f4f4077abdfd60e5393e73c7c411e85e1b3a83507c11af2e656188511475776215df9ca589b4da2295c9455cc399ce1858b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue