mirror of
https://github.com/lone-cloud/gerbil
synced 2026-06-03 09:33:10 -07:00
switch back from xterm, minor fixes
This commit is contained in:
parent
312f530d1d
commit
fe095491a7
10 changed files with 212 additions and 189 deletions
|
|
@ -1,6 +1,9 @@
|
|||
alsa
|
||||
AMDGPU
|
||||
ansi
|
||||
ANSI
|
||||
APPIMAGE
|
||||
stripansi
|
||||
asar
|
||||
Autoencoder
|
||||
BLAS
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@
|
|||
"@types/node": "^24.3.0",
|
||||
"@types/react": "^19.1.11",
|
||||
"@types/react-dom": "^19.1.7",
|
||||
"@types/strip-ansi": "^5.2.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.40.0",
|
||||
"@typescript-eslint/parser": "^8.40.0",
|
||||
"@vitejs/plugin-react": "^5.0.1",
|
||||
|
|
@ -88,14 +89,12 @@
|
|||
"dependencies": {
|
||||
"@mantine/core": "^8.2.7",
|
||||
"@mantine/hooks": "^8.2.7",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
"@xterm/addon-web-links": "^0.11.0",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
"execa": "^9.6.0",
|
||||
"got": "^14.4.7",
|
||||
"lucide-react": "^0.541.0",
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1",
|
||||
"strip-ansi": "^7.1.0",
|
||||
"systeminformation": "^5.27.7",
|
||||
"winston": "^3.17.0",
|
||||
"winston-daily-rotate-file": "^5.0.0",
|
||||
|
|
|
|||
|
|
@ -109,8 +109,8 @@ export const AppHeader = ({
|
|||
|
||||
{currentScreen === 'interface' && (
|
||||
<Select
|
||||
value={activeInterfaceTab}
|
||||
onChange={setActiveInterfaceTab}
|
||||
value={activeInterfaceTab || 'terminal'}
|
||||
onChange={(value) => setActiveInterfaceTab(value || 'terminal')}
|
||||
data={[
|
||||
{
|
||||
value: 'chat',
|
||||
|
|
@ -118,7 +118,7 @@ export const AppHeader = ({
|
|||
},
|
||||
{ value: 'terminal', label: 'Terminal' },
|
||||
]}
|
||||
placeholder="Select view"
|
||||
allowDeselect={false}
|
||||
styles={{
|
||||
input: {
|
||||
minWidth: '9.375rem',
|
||||
|
|
|
|||
|
|
@ -27,17 +27,6 @@ export const ModelFileField = ({
|
|||
}: ModelFileFieldProps) => {
|
||||
const validationState = getInputValidationState(value);
|
||||
|
||||
const getInputColor = () => {
|
||||
switch (validationState) {
|
||||
case 'valid':
|
||||
return 'green';
|
||||
case 'invalid':
|
||||
return 'red';
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const getHelperText = () => {
|
||||
if (!value.trim()) return undefined;
|
||||
|
||||
|
|
@ -62,7 +51,6 @@ export const ModelFileField = ({
|
|||
placeholder={placeholder}
|
||||
value={value}
|
||||
onChange={(event) => onChange(event.currentTarget.value)}
|
||||
color={getInputColor()}
|
||||
error={validationState === 'invalid' ? getHelperText() : undefined}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
import { useEffect, useRef } from 'react';
|
||||
import { Box, useComputedColorScheme } from '@mantine/core';
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
import { FitAddon } from '@xterm/addon-fit';
|
||||
import { WebLinksAddon } from '@xterm/addon-web-links';
|
||||
import '@xterm/xterm/css/xterm.css';
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import {
|
||||
Box,
|
||||
ScrollArea,
|
||||
Text,
|
||||
ActionIcon,
|
||||
useComputedColorScheme,
|
||||
} from '@mantine/core';
|
||||
import { ChevronDown } from 'lucide-react';
|
||||
import styles from '@/styles/layout.module.css';
|
||||
import { UI } from '@/constants';
|
||||
import { handleTerminalOutput } from '@/utils';
|
||||
|
||||
interface TerminalTabProps {
|
||||
onServerReady?: (serverUrl: string) => void;
|
||||
|
|
@ -14,105 +19,42 @@ export const TerminalTab = ({ onServerReady }: TerminalTabProps) => {
|
|||
const computedColorScheme = useComputedColorScheme('light', {
|
||||
getInitialValueInEffect: false,
|
||||
});
|
||||
const terminalRef = useRef<HTMLDivElement>(null);
|
||||
const xtermRef = useRef<Terminal | null>(null);
|
||||
const fitAddonRef = useRef<FitAddon | null>(null);
|
||||
const [terminalContent, setTerminalContent] = useState<string>('');
|
||||
const [isUserScrolling, setIsUserScrolling] = useState<boolean>(false);
|
||||
const [shouldAutoScroll, setShouldAutoScroll] = useState<boolean>(true);
|
||||
const scrollAreaRef = useRef<HTMLDivElement>(null);
|
||||
const viewportRef = useRef<HTMLDivElement>(null);
|
||||
const lastScrollTop = useRef<number>(0);
|
||||
|
||||
const isDark = computedColorScheme === 'dark';
|
||||
|
||||
useEffect(() => {
|
||||
if (!terminalRef.current) return;
|
||||
const handleScroll = ({ y }: { y: number }) => {
|
||||
if (!viewportRef.current) return;
|
||||
|
||||
const terminal = new Terminal({
|
||||
theme: {
|
||||
background: '#1a1b1e',
|
||||
foreground: '#ffffff',
|
||||
cursor: '#ffffff',
|
||||
selectionBackground: '#264f78',
|
||||
},
|
||||
fontFamily:
|
||||
'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
|
||||
fontSize: 14,
|
||||
lineHeight: 1.4,
|
||||
cursorBlink: false,
|
||||
disableStdin: true,
|
||||
allowTransparency: false,
|
||||
scrollback: 10000,
|
||||
convertEol: true,
|
||||
smoothScrollDuration: 0,
|
||||
});
|
||||
const { scrollHeight, clientHeight } = viewportRef.current;
|
||||
const isAtBottomNow = y + clientHeight >= scrollHeight - 10;
|
||||
|
||||
const fitAddon = new FitAddon();
|
||||
const webLinksAddon = new WebLinksAddon();
|
||||
if (y < lastScrollTop.current) {
|
||||
setIsUserScrolling(true);
|
||||
setShouldAutoScroll(false);
|
||||
} else if (isAtBottomNow) {
|
||||
setIsUserScrolling(false);
|
||||
setShouldAutoScroll(true);
|
||||
}
|
||||
|
||||
terminal.loadAddon(fitAddon);
|
||||
terminal.loadAddon(webLinksAddon);
|
||||
|
||||
terminal.open(terminalRef.current);
|
||||
|
||||
setTimeout(() => {
|
||||
fitAddon.fit();
|
||||
}, 100);
|
||||
|
||||
xtermRef.current = terminal;
|
||||
fitAddonRef.current = fitAddon;
|
||||
|
||||
terminal.writeln('\x1b[90mStarting KoboldCpp...\x1b[0m');
|
||||
|
||||
return () => {
|
||||
terminal.dispose();
|
||||
xtermRef.current = null;
|
||||
fitAddonRef.current = null;
|
||||
lastScrollTop.current = y;
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (xtermRef.current) {
|
||||
const newTheme = {
|
||||
background: isDark ? '#1a1b1e' : '#ffffff',
|
||||
foreground: isDark ? '#ffffff' : '#000000',
|
||||
cursor: isDark ? '#ffffff' : '#000000',
|
||||
selectionBackground: isDark ? '#264f78' : '#0078d4',
|
||||
};
|
||||
|
||||
xtermRef.current.options.theme = newTheme;
|
||||
|
||||
const element = xtermRef.current.element;
|
||||
if (element) {
|
||||
element.style.backgroundColor = newTheme.background;
|
||||
element.style.color = newTheme.foreground;
|
||||
if (shouldAutoScroll && !isUserScrolling && viewportRef.current) {
|
||||
const viewport = viewportRef.current;
|
||||
viewport.scrollTop = viewport.scrollHeight;
|
||||
}
|
||||
|
||||
xtermRef.current.refresh(0, xtermRef.current.rows - 1);
|
||||
}
|
||||
}, [isDark]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
if (fitAddonRef.current && xtermRef.current) {
|
||||
setTimeout(() => {
|
||||
fitAddonRef.current?.fit();
|
||||
}, 50);
|
||||
}
|
||||
};
|
||||
|
||||
const resizeObserver = new ResizeObserver(handleResize);
|
||||
if (terminalRef.current) {
|
||||
resizeObserver.observe(terminalRef.current);
|
||||
}
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
resizeObserver.disconnect();
|
||||
};
|
||||
}, []);
|
||||
}, [terminalContent, shouldAutoScroll, isUserScrolling]);
|
||||
|
||||
useEffect(() => {
|
||||
const cleanup = window.electronAPI.kobold.onKoboldOutput((data: string) => {
|
||||
if (!xtermRef.current) return;
|
||||
|
||||
setTerminalContent((prev) => {
|
||||
const newData = data.toString();
|
||||
|
||||
if (
|
||||
|
|
@ -128,38 +70,88 @@ export const TerminalTab = ({ onServerReady }: TerminalTabProps) => {
|
|||
}
|
||||
}
|
||||
|
||||
xtermRef.current.write(newData);
|
||||
xtermRef.current.scrollToBottom();
|
||||
return handleTerminalOutput(prev, newData);
|
||||
});
|
||||
});
|
||||
|
||||
return cleanup;
|
||||
}, [onServerReady]);
|
||||
|
||||
const scrollToBottom = () => {
|
||||
if (viewportRef.current) {
|
||||
const viewport = viewportRef.current;
|
||||
viewport.scrollTop = viewport.scrollHeight;
|
||||
setShouldAutoScroll(true);
|
||||
setIsUserScrolling(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
style={{
|
||||
height: `calc(100vh - ${UI.HEADER_HEIGHT}px)`,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
backgroundColor: isDark
|
||||
? 'var(--mantine-color-dark-filled)'
|
||||
: 'var(--mantine-color-gray-0)',
|
||||
borderRadius: 'inherit',
|
||||
padding: '0.5rem',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
<ScrollArea
|
||||
ref={scrollAreaRef}
|
||||
viewportRef={viewportRef}
|
||||
onScrollPositionChange={handleScroll}
|
||||
className={styles.terminalScrollArea}
|
||||
scrollbarSize={8}
|
||||
offsetScrollbars={false}
|
||||
>
|
||||
<Box p="md">
|
||||
{terminalContent.length === 0 ? (
|
||||
<Text c="dimmed" style={{ fontFamily: 'inherit' }}>
|
||||
Starting KoboldCpp...
|
||||
</Text>
|
||||
) : (
|
||||
<div
|
||||
ref={terminalRef}
|
||||
style={{
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
flex: 1,
|
||||
minHeight: 0,
|
||||
backgroundColor: isDark ? '#1a1b1e' : '#ffffff',
|
||||
borderRadius: '0.25rem',
|
||||
overflow: 'hidden',
|
||||
margin: 0,
|
||||
fontFamily:
|
||||
'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
|
||||
fontSize: '14px',
|
||||
lineHeight: 1.4,
|
||||
color: isDark
|
||||
? 'var(--mantine-color-gray-0)'
|
||||
: 'var(--mantine-color-dark-filled)',
|
||||
whiteSpace: 'pre-wrap',
|
||||
wordBreak: 'break-word',
|
||||
}}
|
||||
/>
|
||||
>
|
||||
{terminalContent}
|
||||
</div>
|
||||
)}
|
||||
</Box>
|
||||
</ScrollArea>
|
||||
|
||||
{isUserScrolling && !shouldAutoScroll && (
|
||||
<ActionIcon
|
||||
variant="filled"
|
||||
color="blue"
|
||||
size="lg"
|
||||
radius="xl"
|
||||
onClick={scrollToBottom}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: '20px',
|
||||
right: '20px',
|
||||
zIndex: 10,
|
||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.3)',
|
||||
}}
|
||||
aria-label="Scroll to bottom"
|
||||
>
|
||||
<ChevronDown size={20} />
|
||||
</ActionIcon>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -21,17 +21,6 @@ export const GeneralTab = ({ onBackendsReady }: GeneralTabProps) => {
|
|||
|
||||
const validationState = getInputValidationState(modelPath);
|
||||
|
||||
const getInputColor = () => {
|
||||
switch (validationState) {
|
||||
case 'valid':
|
||||
return 'green';
|
||||
case 'invalid':
|
||||
return 'red';
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const getHelperText = () => {
|
||||
if (!modelPath.trim()) return undefined;
|
||||
|
||||
|
|
@ -56,7 +45,6 @@ export const GeneralTab = ({ onBackendsReady }: GeneralTabProps) => {
|
|||
placeholder="Select a .gguf model file or enter a direct URL to file"
|
||||
value={modelPath}
|
||||
onChange={(e) => handleModelPathChange(e.target.value)}
|
||||
color={getInputColor()}
|
||||
error={
|
||||
validationState === 'invalid' ? getHelperText() : undefined
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Card, Container, Stack, Tabs, Group, Button } from '@mantine/core';
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { useLaunchConfig } from '@/hooks/useLaunchConfig';
|
||||
import { useLaunchLogic } from '@/hooks/useLaunchLogic';
|
||||
import { useWarnings } from '@/hooks/useWarnings';
|
||||
|
|
@ -26,6 +26,7 @@ export const LaunchScreen = ({
|
|||
const [, setInstallDir] = useState<string>('');
|
||||
const [activeTab, setActiveTab] = useState<string | null>('general');
|
||||
const [configLoaded, setConfigLoaded] = useState<boolean>(false);
|
||||
const defaultsSetRef = useRef(false);
|
||||
|
||||
const {
|
||||
gpuLayers,
|
||||
|
|
@ -86,22 +87,43 @@ export const LaunchScreen = ({
|
|||
if (!backend && backends.length > 0) {
|
||||
handleBackendChange(backends[0].value);
|
||||
}
|
||||
|
||||
if (!modelPath.trim() && !sdmodel.trim()) {
|
||||
handleModelPathChange(DEFAULT_MODEL_URL);
|
||||
}
|
||||
} catch (error) {
|
||||
window.electronAPI.logs.logError(
|
||||
'Failed to set defaults:',
|
||||
error as Error
|
||||
);
|
||||
}
|
||||
}, [backend, modelPath, sdmodel, handleBackendChange, handleModelPathChange]);
|
||||
}, [backend, handleBackendChange]);
|
||||
|
||||
const setInitialDefaults = useCallback(
|
||||
async (currentModelPath: string, currentSdModel: string) => {
|
||||
try {
|
||||
if (
|
||||
!defaultsSetRef.current &&
|
||||
!currentModelPath.trim() &&
|
||||
!currentSdModel.trim()
|
||||
) {
|
||||
handleModelPathChange(DEFAULT_MODEL_URL);
|
||||
defaultsSetRef.current = true;
|
||||
}
|
||||
} catch (error) {
|
||||
window.electronAPI.logs.logError(
|
||||
'Failed to set initial defaults:',
|
||||
error as Error
|
||||
);
|
||||
}
|
||||
},
|
||||
[handleModelPathChange]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (configLoaded) {
|
||||
if (configLoaded && !defaultsSetRef.current) {
|
||||
void setHappyDefaults();
|
||||
if (!modelPath.trim() && !sdmodel.trim()) {
|
||||
void setInitialDefaults(modelPath, sdmodel);
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [configLoaded, setHappyDefaults]);
|
||||
|
||||
const loadConfigFiles = useCallback(async () => {
|
||||
|
|
|
|||
|
|
@ -5,5 +5,6 @@ export * from './hardware';
|
|||
export * from './imageModelPresets';
|
||||
export * from './platform';
|
||||
export * from './sounds';
|
||||
export * from './terminal';
|
||||
export * from './validation';
|
||||
export * from './versionUtils';
|
||||
|
|
|
|||
28
src/utils/terminal.ts
Normal file
28
src/utils/terminal.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import stripAnsi from 'strip-ansi';
|
||||
|
||||
export const handleTerminalOutput = (
|
||||
prevContent: string,
|
||||
newData: string
|
||||
): string => {
|
||||
try {
|
||||
const cleanData = stripAnsi(newData);
|
||||
|
||||
if (cleanData.includes('\r')) {
|
||||
const lines = (prevContent + cleanData).split('\n');
|
||||
return lines
|
||||
.map((line) => {
|
||||
if (line.includes('\r')) {
|
||||
const parts = line.split('\r');
|
||||
return parts[parts.length - 1];
|
||||
}
|
||||
return line;
|
||||
})
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
return prevContent + cleanData;
|
||||
} catch (error) {
|
||||
window.electronAPI.logs.logError('Terminal Basic Error', error as Error);
|
||||
return prevContent + newData;
|
||||
}
|
||||
};
|
||||
60
yarn.lock
60
yarn.lock
|
|
@ -1923,6 +1923,15 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/strip-ansi@npm:^5.2.1":
|
||||
version: 5.2.1
|
||||
resolution: "@types/strip-ansi@npm:5.2.1"
|
||||
dependencies:
|
||||
strip-ansi: "npm:*"
|
||||
checksum: 10c0/052d73697ac18bf12ffc76e8695d65d8588ee53f68cd952870991c3e3bf0c1214d42aa13004b140c1095a8ea27d4915105bafa8be43869bd66e5badca75d2d36
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/triple-beam@npm:^1.3.2":
|
||||
version: 1.3.5
|
||||
resolution: "@types/triple-beam@npm:1.3.5"
|
||||
|
|
@ -2106,31 +2115,6 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@xterm/addon-fit@npm:^0.10.0":
|
||||
version: 0.10.0
|
||||
resolution: "@xterm/addon-fit@npm:0.10.0"
|
||||
peerDependencies:
|
||||
"@xterm/xterm": ^5.0.0
|
||||
checksum: 10c0/76926120fc940376afef2cb68b15aec2a99fc628b6e3cc84f2bcb1682ca9b87f982b3c10ff206faf4ebc5b410467b81a7b5e83be37b4ac386586f472e4fa1c61
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@xterm/addon-web-links@npm:^0.11.0":
|
||||
version: 0.11.0
|
||||
resolution: "@xterm/addon-web-links@npm:0.11.0"
|
||||
peerDependencies:
|
||||
"@xterm/xterm": ^5.0.0
|
||||
checksum: 10c0/9426bed80afa954b0ea97771d041eb44e77a64e560ce8b8ef507a5d3a763979af18ae9f74ed54007bb7e235d0daf035be2a33f90d8edfecb431caf8ba0b0664e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@xterm/xterm@npm:^5.5.0":
|
||||
version: 5.5.0
|
||||
resolution: "@xterm/xterm@npm:5.5.0"
|
||||
checksum: 10c0/358801feece58617d777b2783bec68dac1f52f736da3b0317f71a34f4e25431fb0b1920244f678b8d673f797145b4858c2a5ccb463a4a6df7c10c9093f1c9267
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"abbrev@npm:^1.0.0":
|
||||
version: 1.1.1
|
||||
resolution: "abbrev@npm:1.1.1"
|
||||
|
|
@ -2258,6 +2242,17 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ansi-to-html@npm:^0.7.2":
|
||||
version: 0.7.2
|
||||
resolution: "ansi-to-html@npm:0.7.2"
|
||||
dependencies:
|
||||
entities: "npm:^2.2.0"
|
||||
bin:
|
||||
ansi-to-html: bin/ansi-to-html
|
||||
checksum: 10c0/031da78f716e7c6b0e391c64f7bc5e95f2d37123dcc3237d8c592dc35830dd0da05e0c3f3e3f8179856cfe5fd85c689d2ad85024b71b50014da9ef6e8fa021cf
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"app-builder-bin@npm:5.0.0-alpha.12":
|
||||
version: 5.0.0-alpha.12
|
||||
resolution: "app-builder-bin@npm:5.0.0-alpha.12"
|
||||
|
|
@ -3617,6 +3612,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"entities@npm:^2.2.0":
|
||||
version: 2.2.0
|
||||
resolution: "entities@npm:2.2.0"
|
||||
checksum: 10c0/7fba6af1f116300d2ba1c5673fc218af1961b20908638391b4e1e6d5850314ee2ac3ec22d741b3a8060479911c99305164aed19b6254bde75e7e6b1b2c3f3aa3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"env-paths@npm:^2.2.0":
|
||||
version: 2.2.1
|
||||
resolution: "env-paths@npm:2.2.1"
|
||||
|
|
@ -4413,12 +4415,11 @@ __metadata:
|
|||
"@types/node": "npm:^24.3.0"
|
||||
"@types/react": "npm:^19.1.11"
|
||||
"@types/react-dom": "npm:^19.1.7"
|
||||
"@types/strip-ansi": "npm:^5.2.1"
|
||||
"@typescript-eslint/eslint-plugin": "npm:^8.40.0"
|
||||
"@typescript-eslint/parser": "npm:^8.40.0"
|
||||
"@vitejs/plugin-react": "npm:^5.0.1"
|
||||
"@xterm/addon-fit": "npm:^0.10.0"
|
||||
"@xterm/addon-web-links": "npm:^0.11.0"
|
||||
"@xterm/xterm": "npm:^5.5.0"
|
||||
ansi-to-html: "npm:^0.7.2"
|
||||
cross-env: "npm:^10.0.0"
|
||||
cspell: "npm:^9.2.0"
|
||||
electron: "npm:^37.3.1"
|
||||
|
|
@ -4442,6 +4443,7 @@ __metadata:
|
|||
react: "npm:^19.1.1"
|
||||
react-dom: "npm:^19.1.1"
|
||||
rollup-plugin-visualizer: "npm:^6.0.3"
|
||||
strip-ansi: "npm:^7.1.0"
|
||||
systeminformation: "npm:^5.27.7"
|
||||
typescript: "npm:^5.9.2"
|
||||
vite: "npm:^7.1.3"
|
||||
|
|
@ -7813,7 +7815,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"strip-ansi@npm:^7.0.1, strip-ansi@npm:^7.1.0":
|
||||
"strip-ansi@npm:*, strip-ansi@npm:^7.0.1, strip-ansi@npm:^7.1.0":
|
||||
version: 7.1.0
|
||||
resolution: "strip-ansi@npm:7.1.0"
|
||||
dependencies:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue