mirror of
https://github.com/lone-cloud/gerbil
synced 2026-06-03 09:33:10 -07:00
WIP: special sillytavern config changes for windows
This commit is contained in:
parent
2ecbef9108
commit
07ebcb182e
17 changed files with 607 additions and 274 deletions
|
|
@ -83,6 +83,7 @@
|
|||
"@mantine/core": "^8.2.7",
|
||||
"@mantine/hooks": "^8.2.7",
|
||||
"execa": "^9.6.0",
|
||||
"fkill": "^9.0.0",
|
||||
"got": "^14.4.7",
|
||||
"lucide-react": "^0.542.0",
|
||||
"react": "^19.1.1",
|
||||
|
|
|
|||
27
src/App.tsx
27
src/App.tsx
|
|
@ -98,27 +98,17 @@ export const App = () => {
|
|||
};
|
||||
|
||||
checkInstallation();
|
||||
|
||||
const cleanupInstallDirListener =
|
||||
window.electronAPI.kobold.onInstallDirChanged(() => {
|
||||
checkInstallation();
|
||||
});
|
||||
|
||||
const cleanupVersionsListener = window.electronAPI.kobold.onVersionsUpdated(
|
||||
() => {
|
||||
checkInstallation();
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
cleanupInstallDirListener();
|
||||
cleanupVersionsListener();
|
||||
};
|
||||
}, [checkForUpdates]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const handleBinaryUpdate = async (download: DownloadItem) => {
|
||||
try {
|
||||
const success = await sharedHandleDownload('asset', download, true, true);
|
||||
const success = await sharedHandleDownload({
|
||||
type: 'asset',
|
||||
item: download,
|
||||
isUpdate: true,
|
||||
wasCurrentBinary: true,
|
||||
});
|
||||
|
||||
if (success) {
|
||||
dismissUpdate();
|
||||
|
|
@ -286,6 +276,7 @@ export const App = () => {
|
|||
activeTab={activeInterfaceTab}
|
||||
onTabChange={setActiveInterfaceTab}
|
||||
isImageGenerationMode={isImageGenerationMode}
|
||||
frontendPreference={frontendPreference}
|
||||
/>
|
||||
</ScreenTransition>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ export const DownloadCard = ({
|
|||
const buttons = [];
|
||||
|
||||
if (!isInstalled) {
|
||||
buttons.push(
|
||||
return (
|
||||
<Button
|
||||
key="download"
|
||||
variant="filled"
|
||||
|
|
@ -76,7 +76,7 @@ export const DownloadCard = ({
|
|||
);
|
||||
}
|
||||
|
||||
if (isInstalled && !isCurrent && onMakeCurrent) {
|
||||
if (!isCurrent && onMakeCurrent) {
|
||||
buttons.push(
|
||||
<Button
|
||||
key="makeCurrent"
|
||||
|
|
|
|||
|
|
@ -34,12 +34,12 @@ export const DownloadScreen = ({ onDownloadComplete }: DownloadScreenProps) => {
|
|||
setDownloadingAsset(download.name);
|
||||
|
||||
try {
|
||||
const success = await sharedHandleDownload(
|
||||
'asset',
|
||||
download,
|
||||
false,
|
||||
false
|
||||
);
|
||||
const success = await sharedHandleDownload({
|
||||
type: 'asset',
|
||||
item: download,
|
||||
isUpdate: false,
|
||||
wasCurrentBinary: false,
|
||||
});
|
||||
|
||||
if (success) {
|
||||
onDownloadComplete();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useState, useCallback, useEffect } from 'react';
|
||||
import { useState, useCallback } from 'react';
|
||||
import { ServerTab } from '@/components/screens/Interface/ServerTab';
|
||||
import { TerminalTab } from '@/components/screens/Interface/TerminalTab';
|
||||
import type { InterfaceTab, FrontendPreference } from '@/types';
|
||||
|
|
@ -7,35 +7,17 @@ interface InterfaceScreenProps {
|
|||
activeTab?: InterfaceTab | null;
|
||||
onTabChange?: (tab: InterfaceTab) => void;
|
||||
isImageGenerationMode?: boolean;
|
||||
frontendPreference?: FrontendPreference;
|
||||
}
|
||||
|
||||
export const InterfaceScreen = ({
|
||||
activeTab,
|
||||
onTabChange,
|
||||
isImageGenerationMode = false,
|
||||
frontendPreference = 'koboldcpp',
|
||||
}: InterfaceScreenProps) => {
|
||||
const [serverUrl, setServerUrl] = useState<string>('');
|
||||
const [isServerReady, setIsServerReady] = useState<boolean>(false);
|
||||
const [frontendPreference, setFrontendPreference] =
|
||||
useState<FrontendPreference>('koboldcpp');
|
||||
|
||||
useEffect(() => {
|
||||
const loadFrontendPreference = async () => {
|
||||
try {
|
||||
const frontendPreference = (await window.electronAPI.config.get(
|
||||
'frontendPreference'
|
||||
)) as FrontendPreference;
|
||||
setFrontendPreference(frontendPreference || 'koboldcpp');
|
||||
} catch (error) {
|
||||
window.electronAPI.logs.logError(
|
||||
'Failed to load frontend preference:',
|
||||
error as Error
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
loadFrontendPreference();
|
||||
}, []);
|
||||
|
||||
const handleServerReady = useCallback(
|
||||
(url: string) => {
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ export const AdvancedTab = () => {
|
|||
} | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
const isGpuBackend = backend === 'cuda' || backend === 'rocm';
|
||||
|
||||
useEffect(() => {
|
||||
const detectBackendSupport = async () => {
|
||||
try {
|
||||
|
|
@ -99,27 +101,27 @@ export const AdvancedTab = () => {
|
|||
|
||||
<Group gap="lg" align="flex-start" wrap="nowrap">
|
||||
<CheckboxWithTooltip
|
||||
checked={quantmatmul}
|
||||
checked={quantmatmul && isGpuBackend}
|
||||
onChange={handleQuantmatmulChange}
|
||||
label="QuantMatMul"
|
||||
tooltip={
|
||||
backend !== 'cuda' && backend !== 'rocm'
|
||||
!isGpuBackend
|
||||
? 'QuantMatMul is only available for CUDA and ROCm backends.'
|
||||
: 'Enable MMQ mode to use finetuned kernels instead of default CuBLAS/HipBLAS for prompt processing.'
|
||||
}
|
||||
disabled={backend !== 'cuda' && backend !== 'rocm'}
|
||||
disabled={!isGpuBackend}
|
||||
/>
|
||||
|
||||
<CheckboxWithTooltip
|
||||
checked={lowvram}
|
||||
checked={lowvram && isGpuBackend}
|
||||
onChange={handleLowvramChange}
|
||||
label="Low VRAM"
|
||||
tooltip={
|
||||
backend !== 'cuda' && backend !== 'rocm'
|
||||
!isGpuBackend
|
||||
? 'Low VRAM mode is only available for CUDA and ROCm backends.'
|
||||
: 'Avoid offloading KV Cache or scratch buffers to VRAM. Allows more layers to fit, but may result in a speed loss.'
|
||||
}
|
||||
disabled={backend !== 'cuda' && backend !== 'rocm'}
|
||||
disabled={!isGpuBackend}
|
||||
/>
|
||||
</Group>
|
||||
</Stack>
|
||||
|
|
|
|||
|
|
@ -209,12 +209,12 @@ export const VersionsTab = () => {
|
|||
throw new Error('Download not found');
|
||||
}
|
||||
|
||||
const success = await sharedHandleDownload(
|
||||
'asset',
|
||||
download,
|
||||
false,
|
||||
false
|
||||
);
|
||||
const success = await sharedHandleDownload({
|
||||
type: 'asset',
|
||||
item: download,
|
||||
isUpdate: false,
|
||||
wasCurrentBinary: false,
|
||||
});
|
||||
|
||||
if (success) {
|
||||
await loadInstalledVersions();
|
||||
|
|
@ -231,13 +231,12 @@ export const VersionsTab = () => {
|
|||
throw new Error('Download not found');
|
||||
}
|
||||
|
||||
const wasCurrentBinary = version.isCurrent;
|
||||
const success = await sharedHandleDownload(
|
||||
'asset',
|
||||
download,
|
||||
true,
|
||||
wasCurrentBinary
|
||||
);
|
||||
const success = await sharedHandleDownload({
|
||||
type: 'asset',
|
||||
item: download,
|
||||
isUpdate: true,
|
||||
wasCurrentBinary: version.isCurrent,
|
||||
});
|
||||
|
||||
if (success) {
|
||||
await loadInstalledVersions();
|
||||
|
|
|
|||
|
|
@ -20,6 +20,13 @@ interface CachedReleaseData {
|
|||
timestamp: number;
|
||||
}
|
||||
|
||||
interface HandleDownloadParams {
|
||||
type: 'asset' | 'rocm';
|
||||
item?: DownloadItem;
|
||||
isUpdate?: boolean;
|
||||
wasCurrentBinary?: boolean;
|
||||
}
|
||||
|
||||
const CACHE_KEY = 'kobold-releases-cache';
|
||||
const CACHE_DURATION = 60000;
|
||||
|
||||
|
|
@ -182,12 +189,7 @@ interface UseKoboldVersionsReturn {
|
|||
downloadProgress: Record<string, number>;
|
||||
loadRemoteVersions: () => Promise<void>;
|
||||
refresh: () => Promise<void>;
|
||||
handleDownload: (
|
||||
type: 'asset' | 'rocm',
|
||||
item?: DownloadItem,
|
||||
isUpdate?: boolean,
|
||||
wasCurrentBinary?: boolean
|
||||
) => Promise<boolean>;
|
||||
handleDownload: (params: HandleDownloadParams) => Promise<boolean>;
|
||||
setDownloading: (value: string | null) => void;
|
||||
setDownloadProgress: (
|
||||
value:
|
||||
|
|
@ -310,12 +312,12 @@ export const useKoboldVersions = (): UseKoboldVersionsReturn => {
|
|||
}, [platformInfo.platform]);
|
||||
|
||||
const handleDownload = useCallback(
|
||||
async (
|
||||
type: 'asset' | 'rocm',
|
||||
item?: DownloadItem,
|
||||
async ({
|
||||
type,
|
||||
item,
|
||||
isUpdate = false,
|
||||
wasCurrentBinary = false
|
||||
): Promise<boolean> => {
|
||||
wasCurrentBinary = false,
|
||||
}: HandleDownloadParams): Promise<boolean> => {
|
||||
if (type === 'asset' && !item) return false;
|
||||
|
||||
const downloadName = item?.name || 'download';
|
||||
|
|
|
|||
|
|
@ -269,9 +269,10 @@ export const useLaunchLogic = ({
|
|||
onLaunchModeChange(isImageMode);
|
||||
}
|
||||
} else {
|
||||
const errorMessage = result.error || 'Unknown launch error';
|
||||
window.electronAPI.logs.logError(
|
||||
'Launch failed:',
|
||||
new Error(result.error)
|
||||
new Error(errorMessage)
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import type { LogManager } from '@/main/managers/LogManager';
|
|||
import type { SillyTavernManager } from '@/main/managers/SillyTavernManager';
|
||||
import { HardwareService } from '@/main/services/HardwareService';
|
||||
import { BinaryService } from '@/main/services/BinaryService';
|
||||
import type { FrontendPreference } from '@/types';
|
||||
|
||||
export class IPCHandlers {
|
||||
private koboldManager: KoboldCppManager;
|
||||
|
|
@ -39,27 +38,14 @@ export class IPCHandlers {
|
|||
error?: string;
|
||||
}> {
|
||||
try {
|
||||
const finalArgs = [...args];
|
||||
const frontendPreference = (await this.configManager.get(
|
||||
'frontendPreference'
|
||||
)) as FrontendPreference | undefined;
|
||||
|
||||
if (frontendPreference !== 'koboldcpp' && !finalArgs.includes('--cli')) {
|
||||
finalArgs.push('--cli');
|
||||
}
|
||||
const frontendPreference =
|
||||
await this.configManager.get('frontendPreference');
|
||||
|
||||
if (frontendPreference === 'sillytavern') {
|
||||
try {
|
||||
await this.sillyTavernManager.startFrontend(finalArgs);
|
||||
} catch (error) {
|
||||
this.logManager.logError(
|
||||
'Failed to setup SillyTavern:',
|
||||
error as Error
|
||||
);
|
||||
}
|
||||
await this.sillyTavernManager.startFrontend(args);
|
||||
}
|
||||
|
||||
return await this.koboldManager.launchKoboldCpp(finalArgs);
|
||||
return await this.koboldManager.launchKoboldCpp(args);
|
||||
} catch (error) {
|
||||
this.logManager.logError('Error in enhanced launch:', error as Error);
|
||||
return {
|
||||
|
|
@ -147,7 +133,7 @@ export class IPCHandlers {
|
|||
|
||||
ipcMain.handle(
|
||||
'kobold:getAvailableBackends',
|
||||
(_event, includeDisabled = false) =>
|
||||
(_, includeDisabled = false) =>
|
||||
this.binaryService.getAvailableBackends(includeDisabled)
|
||||
);
|
||||
|
||||
|
|
@ -176,9 +162,15 @@ export class IPCHandlers {
|
|||
this.launchKoboldCppWithCustomFrontends(args)
|
||||
);
|
||||
|
||||
ipcMain.handle('kobold:stopKoboldCpp', () =>
|
||||
this.koboldManager.stopKoboldCpp()
|
||||
);
|
||||
ipcMain.handle('kobold:stopKoboldCpp', async () => {
|
||||
try {
|
||||
this.koboldManager.stopKoboldCpp();
|
||||
await this.sillyTavernManager.cleanup();
|
||||
} catch (error) {
|
||||
this.logManager.logError('Error during stop/cleanup:', error as Error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('kobold:parseConfigFile', (_event, filePath) =>
|
||||
this.koboldManager.parseConfigFile(filePath)
|
||||
|
|
|
|||
|
|
@ -61,7 +61,8 @@ export class ConfigManager {
|
|||
}
|
||||
|
||||
getCurrentKoboldBinary(): string | undefined {
|
||||
return this.config.currentKoboldBinary as string | undefined;
|
||||
const path = this.config.currentKoboldBinary as string | undefined;
|
||||
return path ? path.trim() : path;
|
||||
}
|
||||
|
||||
setCurrentKoboldBinary(binaryPath: string) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/* eslint-disable no-comments/disallowComments */
|
||||
import { spawn, ChildProcess, exec as execCmd } from 'child_process';
|
||||
import { spawn, ChildProcess } from 'child_process';
|
||||
import { join } from 'path';
|
||||
import {
|
||||
existsSync,
|
||||
|
|
@ -13,6 +13,8 @@ import {
|
|||
} from 'fs';
|
||||
import { rm } from 'fs/promises';
|
||||
import { dialog } from 'electron';
|
||||
|
||||
import { terminateProcess } from '@/utils/processUtils';
|
||||
import { execa } from 'execa';
|
||||
import { got } from 'got';
|
||||
import { pipeline } from 'stream/promises';
|
||||
|
|
@ -46,6 +48,37 @@ export class KoboldCppManager {
|
|||
this.installDir = this.configManager.getInstallDir() || '';
|
||||
}
|
||||
|
||||
private async removeDirectoryWithRetry(
|
||||
dirPath: string,
|
||||
maxRetries = 3,
|
||||
delayMs = 1000
|
||||
): Promise<void> {
|
||||
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
await rm(dirPath, { recursive: true, force: true });
|
||||
return;
|
||||
} catch (error) {
|
||||
const isLastAttempt = attempt === maxRetries;
|
||||
const isPermissionError =
|
||||
(error as Error & { code?: string }).code === 'EPERM';
|
||||
|
||||
if (isLastAttempt) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (isPermissionError && process.platform === 'win32') {
|
||||
this.windowManager.sendKoboldOutput(
|
||||
`Attempt ${attempt}/${maxRetries} failed (file in use), retrying in ${delayMs}ms...`
|
||||
);
|
||||
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
||||
delayMs *= 1.5;
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async downloadRelease(
|
||||
asset: GitHubAsset,
|
||||
onProgress?: (progress: number) => void
|
||||
|
|
@ -56,12 +89,26 @@ export class KoboldCppManager {
|
|||
|
||||
if (asset.isUpdate && existsSync(unpackedDirPath)) {
|
||||
try {
|
||||
await rm(unpackedDirPath, { recursive: true, force: true });
|
||||
if (this.koboldProcess && !this.koboldProcess.killed) {
|
||||
this.windowManager.sendKoboldOutput(
|
||||
'Stopping KoboldCpp process before update...'
|
||||
);
|
||||
|
||||
await this.cleanup();
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
}
|
||||
|
||||
await this.removeDirectoryWithRetry(unpackedDirPath);
|
||||
} catch (error) {
|
||||
this.logManager.logError(
|
||||
'Failed to remove existing directory for update:',
|
||||
error as Error
|
||||
);
|
||||
throw new Error(
|
||||
`Cannot update: Failed to remove existing installation. ` +
|
||||
`Please ensure KoboldCpp is stopped and try again. ` +
|
||||
`Error: ${(error as Error).message}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -698,10 +745,15 @@ export class KoboldCppManager {
|
|||
|
||||
const currentVersion = await this.getCurrentVersion();
|
||||
if (!currentVersion || !existsSync(currentVersion.path)) {
|
||||
const error = 'KoboldCpp not found';
|
||||
const rawPath = this.configManager.getCurrentKoboldBinary();
|
||||
const error = currentVersion
|
||||
? `KoboldCpp binary file does not exist at path: ${currentVersion.path}`
|
||||
: 'No KoboldCpp version configured';
|
||||
|
||||
this.logManager.logError(
|
||||
`Launch failed: ${error}. Current version: ${JSON.stringify(currentVersion)}`
|
||||
`Launch failed: ${error}. Raw config path: "${rawPath}", Current version: ${JSON.stringify(currentVersion)}`
|
||||
);
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error,
|
||||
|
|
@ -801,75 +853,10 @@ export class KoboldCppManager {
|
|||
|
||||
async cleanup(): Promise<void> {
|
||||
if (this.koboldProcess) {
|
||||
await new Promise<void>((resolve) => {
|
||||
if (!this.koboldProcess) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
const cleanup = () => {
|
||||
this.koboldProcess = null;
|
||||
resolve();
|
||||
};
|
||||
|
||||
this.koboldProcess.once('exit', cleanup);
|
||||
this.koboldProcess.once('error', cleanup);
|
||||
|
||||
try {
|
||||
if (process.platform === 'win32') {
|
||||
const pid = this.koboldProcess.pid;
|
||||
if (pid) {
|
||||
try {
|
||||
this.koboldProcess.kill('SIGTERM');
|
||||
|
||||
setTimeout(() => {
|
||||
if (this.koboldProcess && !this.koboldProcess.killed) {
|
||||
execCmd(
|
||||
`taskkill /pid ${pid} /t /f`,
|
||||
(error: Error | null) => {
|
||||
if (error) {
|
||||
this.logManager.logError(
|
||||
'Error force-killing process:',
|
||||
error
|
||||
);
|
||||
}
|
||||
cleanup();
|
||||
}
|
||||
);
|
||||
} else {
|
||||
cleanup();
|
||||
}
|
||||
}, 2000);
|
||||
} catch (error) {
|
||||
this.logManager.logError(
|
||||
'Error during Windows cleanup:',
|
||||
error as Error
|
||||
);
|
||||
cleanup();
|
||||
}
|
||||
} else {
|
||||
cleanup();
|
||||
}
|
||||
} else {
|
||||
// Unix-like systems
|
||||
this.koboldProcess.kill('SIGTERM');
|
||||
|
||||
setTimeout(() => {
|
||||
if (this.koboldProcess && !this.koboldProcess.killed) {
|
||||
try {
|
||||
this.koboldProcess.kill('SIGKILL');
|
||||
} catch {
|
||||
void 0;
|
||||
}
|
||||
}
|
||||
cleanup();
|
||||
}, 3000);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logManager.logError('Error during cleanup:', error as Error);
|
||||
cleanup();
|
||||
}
|
||||
await terminateProcess(this.koboldProcess, {
|
||||
logError: (message, error) => this.logManager.logError(message, error),
|
||||
});
|
||||
this.koboldProcess = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,6 +44,20 @@ export class LogManager {
|
|||
|
||||
public logError(message: string, error?: Error) {
|
||||
this.logger.error(message, { error });
|
||||
this.flush();
|
||||
}
|
||||
|
||||
public flush() {
|
||||
const fileTransport = this.logger.transports.find(
|
||||
(t) => t.constructor.name === 'DailyRotateFile'
|
||||
);
|
||||
if (
|
||||
fileTransport &&
|
||||
'flush' in fileTransport &&
|
||||
typeof fileTransport.flush === 'function'
|
||||
) {
|
||||
(fileTransport as { flush: () => void }).flush();
|
||||
}
|
||||
}
|
||||
|
||||
public logDebug(message: string) {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import type { ChildProcess } from 'child_process';
|
|||
import { LogManager } from './LogManager';
|
||||
import { WindowManager } from './WindowManager';
|
||||
import { SILLYTAVERN } from '@/constants';
|
||||
import { terminateProcess, killProcessOnPort } from '@/utils/processUtils';
|
||||
|
||||
export interface SillyTavernConfig {
|
||||
name: string;
|
||||
|
|
@ -38,53 +39,83 @@ export class SillyTavernManager {
|
|||
});
|
||||
}
|
||||
|
||||
private getSillyTavernSettingsPath(): string {
|
||||
private detectedDataRoot: string | null = null;
|
||||
|
||||
private getFallbackDataRoot(): string {
|
||||
const platform = process.platform;
|
||||
const home = homedir();
|
||||
|
||||
switch (platform) {
|
||||
case 'win32':
|
||||
return join(
|
||||
home,
|
||||
'AppData',
|
||||
'Roaming',
|
||||
'SillyTavern',
|
||||
'data',
|
||||
'default-user',
|
||||
'settings.json'
|
||||
);
|
||||
return join(home, 'AppData', 'Local', 'SillyTavern', 'Data', 'data');
|
||||
case 'darwin':
|
||||
return join(
|
||||
home,
|
||||
'Library',
|
||||
'Application Support',
|
||||
'SillyTavern',
|
||||
'data',
|
||||
'default-user',
|
||||
'settings.json'
|
||||
'data'
|
||||
);
|
||||
case 'linux':
|
||||
default:
|
||||
return join(
|
||||
home,
|
||||
'.local',
|
||||
'share',
|
||||
'SillyTavern',
|
||||
'data',
|
||||
'default-user',
|
||||
'settings.json'
|
||||
);
|
||||
return join(home, '.local', 'share', 'SillyTavern', 'data');
|
||||
}
|
||||
}
|
||||
|
||||
private getSillyTavernDataRoot(): string {
|
||||
if (this.detectedDataRoot) {
|
||||
return this.detectedDataRoot;
|
||||
}
|
||||
|
||||
const fallback = this.getFallbackDataRoot();
|
||||
|
||||
if (existsSync(fallback)) {
|
||||
this.detectedDataRoot = fallback;
|
||||
return fallback;
|
||||
}
|
||||
|
||||
const platform = process.platform;
|
||||
const home = homedir();
|
||||
const alternatePaths = [];
|
||||
|
||||
switch (platform) {
|
||||
case 'win32':
|
||||
alternatePaths.push(
|
||||
join(home, 'AppData', 'Local', 'SillyTavern', 'data'),
|
||||
join(home, '.sillytavern', 'data')
|
||||
);
|
||||
break;
|
||||
case 'darwin':
|
||||
alternatePaths.push(join(home, '.sillytavern', 'data'));
|
||||
break;
|
||||
case 'linux':
|
||||
default:
|
||||
alternatePaths.push(join(home, '.sillytavern', 'data'));
|
||||
break;
|
||||
}
|
||||
|
||||
for (const path of alternatePaths) {
|
||||
if (existsSync(path)) {
|
||||
this.detectedDataRoot = path;
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
this.detectedDataRoot = fallback;
|
||||
return fallback;
|
||||
}
|
||||
|
||||
private getSillyTavernSettingsPath(): string {
|
||||
return join(this.getSillyTavernDataRoot(), 'default-user', 'settings.json');
|
||||
}
|
||||
|
||||
private getSillyTavernBaseArgs(): string[] {
|
||||
return [
|
||||
'sillytavern',
|
||||
'--listen',
|
||||
'--browserLaunchEnabled',
|
||||
'false',
|
||||
'--securityOverride',
|
||||
'true',
|
||||
'--disableCsrf',
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -117,6 +148,10 @@ export class SillyTavernManager {
|
|||
return spawn('npx', args, {
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
detached: false,
|
||||
env: {
|
||||
...process.env,
|
||||
DATA_ROOT: this.getSillyTavernDataRoot(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -143,35 +178,37 @@ export class SillyTavernManager {
|
|||
|
||||
let hasResolved = false;
|
||||
|
||||
const cleanupAndResolve = () => {
|
||||
initProcess.on('exit', (code: number | null, signal: string | null) => {
|
||||
const exitMessage = signal
|
||||
? `SillyTavern init process terminated with signal ${signal}`
|
||||
: `SillyTavern init process exited with code ${code}`;
|
||||
this.windowManager.sendKoboldOutput(exitMessage);
|
||||
|
||||
if (!hasResolved) {
|
||||
hasResolved = true;
|
||||
|
||||
if (code !== 0) {
|
||||
const errorMsg =
|
||||
code === 4294963214
|
||||
? 'SillyTavern failed to install due to EBUSY error (resource busy or locked). This is a critical error.'
|
||||
: `SillyTavern initialization failed with exit code ${code}`;
|
||||
|
||||
this.logManager.logError(
|
||||
'SillyTavern initialization failed:',
|
||||
new Error(errorMsg)
|
||||
);
|
||||
this.windowManager.sendKoboldOutput(`CRITICAL ERROR: ${errorMsg}`);
|
||||
reject(new Error(errorMsg));
|
||||
} else {
|
||||
this.windowManager.sendKoboldOutput(
|
||||
'SillyTavern settings should now be generated'
|
||||
);
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
if (!initProcess.killed) {
|
||||
initProcess.kill('SIGTERM');
|
||||
}
|
||||
cleanupAndResolve();
|
||||
}, 90000);
|
||||
|
||||
initProcess.on('exit', (code: number | null, signal: string | null) => {
|
||||
clearTimeout(timeout);
|
||||
const exitMessage = signal
|
||||
? `SillyTavern init process terminated with signal ${signal}`
|
||||
: `SillyTavern init process exited with code ${code}`;
|
||||
this.windowManager.sendKoboldOutput(exitMessage);
|
||||
cleanupAndResolve();
|
||||
});
|
||||
|
||||
initProcess.on('error', (error) => {
|
||||
clearTimeout(timeout);
|
||||
|
||||
if (!hasResolved) {
|
||||
hasResolved = true;
|
||||
this.logManager.logError(
|
||||
|
|
@ -192,8 +229,12 @@ export class SillyTavernManager {
|
|||
if (output.includes('SillyTavern is listening')) {
|
||||
setTimeout(() => {
|
||||
if (!initProcess.killed && !hasResolved) {
|
||||
hasResolved = true;
|
||||
initProcess.kill('SIGTERM');
|
||||
cleanupAndResolve();
|
||||
this.windowManager.sendKoboldOutput(
|
||||
'SillyTavern settings should now be generated'
|
||||
);
|
||||
resolve();
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
|
@ -206,14 +247,6 @@ export class SillyTavernManager {
|
|||
this.windowManager.sendKoboldOutput(output.trim());
|
||||
});
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if (!initProcess.killed && !hasResolved) {
|
||||
this.windowManager.sendKoboldOutput(
|
||||
'SillyTavern initialization taking longer than expected, please wait...'
|
||||
);
|
||||
}
|
||||
}, 30000);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -277,7 +310,7 @@ export class SillyTavernManager {
|
|||
|
||||
if (!textgenSettings.server_urls) textgenSettings.server_urls = {};
|
||||
const serverUrls = textgenSettings.server_urls as Record<string, unknown>;
|
||||
serverUrls.koboldcpp = koboldUrl;
|
||||
serverUrls.koboldcpp = koboldApiUrl;
|
||||
|
||||
settings.main_api = 'textgenerationwebui';
|
||||
textgenSettings.type = 'koboldcpp';
|
||||
|
|
@ -288,9 +321,6 @@ export class SillyTavernManager {
|
|||
this.windowManager.sendKoboldOutput(
|
||||
`SillyTavern configuration updated successfully!`
|
||||
);
|
||||
this.windowManager.sendKoboldOutput(
|
||||
`KoboldCpp will auto-connect to: ${koboldApiUrl}`
|
||||
);
|
||||
} catch (error) {
|
||||
this.logManager.logError(
|
||||
'Failed to setup SillyTavern config:',
|
||||
|
|
@ -302,6 +332,39 @@ export class SillyTavernManager {
|
|||
}
|
||||
}
|
||||
|
||||
private async waitForSillyTavernToStart(_port: number): Promise<void> {
|
||||
this.windowManager.sendKoboldOutput('Waiting for SillyTavern to start...');
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
reject(new Error('SillyTavern failed to start within 30 seconds'));
|
||||
}, 30000);
|
||||
|
||||
const checkForOutput = (data: Buffer) => {
|
||||
const output = data.toString();
|
||||
if (output.includes('SillyTavern is listening')) {
|
||||
clearTimeout(timeout);
|
||||
this.windowManager.sendKoboldOutput('SillyTavern is now running!');
|
||||
resolve();
|
||||
|
||||
if (this.sillyTavernProcess?.stdout) {
|
||||
this.sillyTavernProcess.stdout.removeListener(
|
||||
'data',
|
||||
checkForOutput
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (this.sillyTavernProcess?.stdout) {
|
||||
this.sillyTavernProcess.stdout.on('data', checkForOutput);
|
||||
} else {
|
||||
clearTimeout(timeout);
|
||||
reject(new Error('SillyTavern process stdout not available'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private createProxyServer(targetPort: number, proxyPort: number): void {
|
||||
this.proxyServer = createServer((req, res) => {
|
||||
const options = {
|
||||
|
|
@ -351,6 +414,8 @@ export class SillyTavernManager {
|
|||
|
||||
await this.stopFrontend();
|
||||
|
||||
await killProcessOnPort(config.port);
|
||||
|
||||
this.windowManager.sendKoboldOutput(
|
||||
`Preparing SillyTavern to connect to KoboldCpp at ${koboldHost}:${koboldPort}...`
|
||||
);
|
||||
|
|
@ -368,6 +433,11 @@ export class SillyTavernManager {
|
|||
config.port.toString(),
|
||||
];
|
||||
|
||||
this.windowManager.sendKoboldOutput(
|
||||
'Final port check before starting SillyTavern...'
|
||||
);
|
||||
await killProcessOnPort(config.port);
|
||||
|
||||
this.sillyTavernProcess = this.createNpxProcess(sillyTavernArgs);
|
||||
|
||||
if (this.sillyTavernProcess.stdout) {
|
||||
|
|
@ -403,16 +473,8 @@ export class SillyTavernManager {
|
|||
this.sillyTavernProcess = null;
|
||||
});
|
||||
|
||||
if (config.proxyPort) {
|
||||
await this.waitForSillyTavernToStart(config.port);
|
||||
this.createProxyServer(config.port, config.proxyPort);
|
||||
this.windowManager.sendKoboldOutput(
|
||||
`SillyTavern proxy starting at http://localhost:${config.proxyPort} (forwarding to port ${config.port})`
|
||||
);
|
||||
} else {
|
||||
this.windowManager.sendKoboldOutput(
|
||||
`SillyTavern starting at http://localhost:${config.port}`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logManager.logError(
|
||||
`Failed to start SillyTavern: ${error instanceof Error ? error.message : String(error)}`,
|
||||
|
|
@ -423,26 +485,33 @@ export class SillyTavernManager {
|
|||
}
|
||||
|
||||
async stopFrontend(): Promise<void> {
|
||||
this.windowManager.sendKoboldOutput('Stopping SillyTavern frontend...');
|
||||
|
||||
try {
|
||||
this.windowManager.sendKoboldOutput(
|
||||
`Killing processes on port ${SILLYTAVERN.PORT}...`
|
||||
);
|
||||
await killProcessOnPort(SILLYTAVERN.PORT);
|
||||
this.windowManager.sendKoboldOutput(
|
||||
`Port ${SILLYTAVERN.PORT} is now free`
|
||||
);
|
||||
} catch (error) {
|
||||
this.logManager.logError(
|
||||
'Error killing processes on port:',
|
||||
error as Error
|
||||
);
|
||||
}
|
||||
|
||||
const promises: Promise<void>[] = [];
|
||||
|
||||
if (this.sillyTavernProcess) {
|
||||
promises.push(
|
||||
new Promise((resolve) => {
|
||||
const timeout = setTimeout(() => {
|
||||
this.logManager.logError(
|
||||
'SillyTavern did not close within timeout',
|
||||
new Error('Process close timeout')
|
||||
);
|
||||
terminateProcess(this.sillyTavernProcess, {
|
||||
logError: (message, error) =>
|
||||
this.logManager.logError(message, error),
|
||||
}).then(() => {
|
||||
this.sillyTavernProcess = null;
|
||||
resolve();
|
||||
}, 5000);
|
||||
|
||||
this.sillyTavernProcess?.kill('SIGTERM');
|
||||
this.sillyTavernProcess?.once('exit', () => {
|
||||
clearTimeout(timeout);
|
||||
this.sillyTavernProcess = null;
|
||||
resolve();
|
||||
});
|
||||
this.windowManager.sendKoboldOutput('SillyTavern process terminated');
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
@ -452,6 +521,7 @@ export class SillyTavernManager {
|
|||
new Promise((resolve) => {
|
||||
this.proxyServer?.close(() => {
|
||||
this.proxyServer = null;
|
||||
this.windowManager.sendKoboldOutput('Proxy server closed');
|
||||
resolve();
|
||||
});
|
||||
})
|
||||
|
|
@ -459,8 +529,8 @@ export class SillyTavernManager {
|
|||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
this.windowManager.sendKoboldOutput('SillyTavern frontend stopped');
|
||||
}
|
||||
|
||||
async cleanup(): Promise<void> {
|
||||
if (this.sillyTavernProcess) {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@ export class WindowManager {
|
|||
|
||||
private getIconPath(): string {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
return join(__dirname, '../../assets/icon.png');
|
||||
const projectRoot = join(__dirname, '../..');
|
||||
return join(projectRoot, 'src/assets/icon.png');
|
||||
}
|
||||
return join(process.resourcesPath, 'assets/icon.png');
|
||||
}
|
||||
|
|
|
|||
100
src/utils/processUtils.ts
Normal file
100
src/utils/processUtils.ts
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
import type { ChildProcess } from 'child_process';
|
||||
import { exec } from 'child_process';
|
||||
import fkill from 'fkill';
|
||||
|
||||
export interface ProcessTerminationOptions {
|
||||
logError?: (message: string, error: Error) => void;
|
||||
}
|
||||
|
||||
async function findProcessesOnPort(port: number): Promise<number[]> {
|
||||
return new Promise((resolve) => {
|
||||
if (process.platform === 'win32') {
|
||||
exec(`netstat -ano | findstr :${port}`, (error, stdout) => {
|
||||
if (error || !stdout) {
|
||||
resolve([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const pids: number[] = [];
|
||||
const lines = stdout.split('\n');
|
||||
|
||||
for (const line of lines) {
|
||||
const match = line.match(/\s+(\d+)$/);
|
||||
if (match) {
|
||||
const pid = parseInt(match[1], 10);
|
||||
if (!isNaN(pid) && pid > 0) {
|
||||
pids.push(pid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resolve([...new Set(pids)]);
|
||||
});
|
||||
} else {
|
||||
exec(`lsof -ti:${port}`, (error, stdout) => {
|
||||
if (error || !stdout) {
|
||||
resolve([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const pids = stdout
|
||||
.trim()
|
||||
.split('\n')
|
||||
.map((pid) => parseInt(pid, 10))
|
||||
.filter((pid) => !isNaN(pid) && pid > 0);
|
||||
|
||||
resolve(pids);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function isPortFree(port: number): Promise<boolean> {
|
||||
const pids = await findProcessesOnPort(port);
|
||||
return pids.length === 0;
|
||||
}
|
||||
|
||||
export async function killProcessOnPort(port: number): Promise<void> {
|
||||
try {
|
||||
await fkill(`:${port}`, { force: true, silent: true });
|
||||
} catch {
|
||||
void 0;
|
||||
}
|
||||
|
||||
const pids = await findProcessesOnPort(port);
|
||||
|
||||
if (pids.length > 0) {
|
||||
for (const pid of pids) {
|
||||
try {
|
||||
await fkill(pid, { force: true, silent: true });
|
||||
} catch {
|
||||
void 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let attempts = 0;
|
||||
const maxAttempts = 10;
|
||||
|
||||
while (attempts < maxAttempts && !(await isPortFree(port))) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
attempts++;
|
||||
}
|
||||
}
|
||||
|
||||
export async function terminateProcess(
|
||||
process: ChildProcess,
|
||||
options: ProcessTerminationOptions = {}
|
||||
): Promise<void> {
|
||||
const { logError } = options;
|
||||
|
||||
if (!process || !process.pid) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await fkill(process.pid, { force: true, silent: true });
|
||||
} catch (error) {
|
||||
logError?.('Error terminating process:', error as Error);
|
||||
}
|
||||
}
|
||||
220
yarn.lock
220
yarn.lock
|
|
@ -1601,6 +1601,16 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"aggregate-error@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "aggregate-error@npm:5.0.0"
|
||||
dependencies:
|
||||
clean-stack: "npm:^5.2.0"
|
||||
indent-string: "npm:^5.0.0"
|
||||
checksum: 10c0/a5de7138571f514bad76290736f49a0db8809247082f2519037e0c37d03fc8d91d733e079d6b1674feda28a757b1932421ad205b8c0f8794a0c0e5bf1be2315e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ajv-keywords@npm:^3.4.1":
|
||||
version: 3.5.2
|
||||
resolution: "ajv-keywords@npm:3.5.2"
|
||||
|
|
@ -1946,16 +1956,16 @@ __metadata:
|
|||
linkType: hard
|
||||
|
||||
"browserslist@npm:^4.24.0":
|
||||
version: 4.25.3
|
||||
resolution: "browserslist@npm:4.25.3"
|
||||
version: 4.25.4
|
||||
resolution: "browserslist@npm:4.25.4"
|
||||
dependencies:
|
||||
caniuse-lite: "npm:^1.0.30001735"
|
||||
electron-to-chromium: "npm:^1.5.204"
|
||||
caniuse-lite: "npm:^1.0.30001737"
|
||||
electron-to-chromium: "npm:^1.5.211"
|
||||
node-releases: "npm:^2.0.19"
|
||||
update-browserslist-db: "npm:^1.1.3"
|
||||
bin:
|
||||
browserslist: cli.js
|
||||
checksum: 10c0/cefbbf962b7c0f0d77e952a4b4b37469db7f7f02bc2be7297810ac3cf086660f48daf78d00f7aad8a11b682f88b0ee0022594165ead749e9e4158a0aa49cd161
|
||||
checksum: 10c0/2b105948990dc2fc0bc2536b4889aadfa15d637e1d857a121611a704cdf539a68f575a391f6bf8b7ff19db36cee1b7834565571f35a7ea691051d2e7fb4f2eb1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
@ -2168,7 +2178,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"caniuse-lite@npm:^1.0.30001735":
|
||||
"caniuse-lite@npm:^1.0.30001737":
|
||||
version: 1.0.30001737
|
||||
resolution: "caniuse-lite@npm:1.0.30001737"
|
||||
checksum: 10c0/9d9cfe3b46fe670d171cee10c5c1b0fb641946fd5d6bea26149f804003d53d82ade7ef5a4a640fb3a0eaec47c7839b57e06a6ddae4f0ad2cd58e1187d31997ce
|
||||
|
|
@ -2227,6 +2237,15 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"clean-stack@npm:^5.2.0":
|
||||
version: 5.2.0
|
||||
resolution: "clean-stack@npm:5.2.0"
|
||||
dependencies:
|
||||
escape-string-regexp: "npm:5.0.0"
|
||||
checksum: 10c0/0de47a4152e49dcdeede5f47d7bb9a39a3ea748acb1cd2f0160dbee972d920be81390cb4c5566e6b795791b9efb12359e89fdd7c2e63b36025d59529558570f1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cli-cursor@npm:^3.1.0":
|
||||
version: 3.1.0
|
||||
resolution: "cli-cursor@npm:3.1.0"
|
||||
|
|
@ -2448,7 +2467,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cross-spawn@npm:^7.0.1, cross-spawn@npm:^7.0.6":
|
||||
"cross-spawn@npm:^7.0.1, cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.6":
|
||||
version: 7.0.6
|
||||
resolution: "cross-spawn@npm:7.0.6"
|
||||
dependencies:
|
||||
|
|
@ -2745,7 +2764,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"electron-to-chromium@npm:^1.5.204":
|
||||
"electron-to-chromium@npm:^1.5.211":
|
||||
version: 1.5.211
|
||||
resolution: "electron-to-chromium@npm:1.5.211"
|
||||
checksum: 10c0/587536f2e319b7484cd4c9e83484f461ee06672c588c84bf4d4b6a6b5d00fbdb621d4ca418a68125a86db95d373b890b47de2fb5a0f52592cc8aebc263623e6e
|
||||
|
|
@ -2788,9 +2807,9 @@ __metadata:
|
|||
linkType: hard
|
||||
|
||||
"emoji-regex@npm:^10.3.0":
|
||||
version: 10.4.0
|
||||
resolution: "emoji-regex@npm:10.4.0"
|
||||
checksum: 10c0/a3fcedfc58bfcce21a05a5f36a529d81e88d602100145fcca3dc6f795e3c8acc4fc18fe773fbf9b6d6e9371205edb3afa2668ec3473fa2aa7fd47d2a9d46482d
|
||||
version: 10.5.0
|
||||
resolution: "emoji-regex@npm:10.5.0"
|
||||
checksum: 10c0/17cf84335a461fc23bf90575122ace2902630dc760e53299474cd3b0b5e4cfbc6c0223a389a766817538e5d20bf0f36c67b753f27c9e705056af510b8777e312
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
@ -3098,6 +3117,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"escape-string-regexp@npm:5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "escape-string-regexp@npm:5.0.0"
|
||||
checksum: 10c0/6366f474c6f37a802800a435232395e04e9885919873e382b157ab7e8f0feb8fed71497f84a6f6a81a49aab41815522f5839112bd38026d203aea0c91622df95
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"escape-string-regexp@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "escape-string-regexp@npm:4.0.0"
|
||||
|
|
@ -3354,6 +3380,40 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"execa@npm:^6.1.0":
|
||||
version: 6.1.0
|
||||
resolution: "execa@npm:6.1.0"
|
||||
dependencies:
|
||||
cross-spawn: "npm:^7.0.3"
|
||||
get-stream: "npm:^6.0.1"
|
||||
human-signals: "npm:^3.0.1"
|
||||
is-stream: "npm:^3.0.0"
|
||||
merge-stream: "npm:^2.0.0"
|
||||
npm-run-path: "npm:^5.1.0"
|
||||
onetime: "npm:^6.0.0"
|
||||
signal-exit: "npm:^3.0.7"
|
||||
strip-final-newline: "npm:^3.0.0"
|
||||
checksum: 10c0/004ee32092af745766a1b0352fdba8701a4001bc3fe08e63101c04276d4c860bbe11bb8ab85f37acdff13d3da83d60e044041dcf24bd7e25e645a543828d9c41
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"execa@npm:^8.0.1":
|
||||
version: 8.0.1
|
||||
resolution: "execa@npm:8.0.1"
|
||||
dependencies:
|
||||
cross-spawn: "npm:^7.0.3"
|
||||
get-stream: "npm:^8.0.1"
|
||||
human-signals: "npm:^5.0.0"
|
||||
is-stream: "npm:^3.0.0"
|
||||
merge-stream: "npm:^2.0.0"
|
||||
npm-run-path: "npm:^5.1.0"
|
||||
onetime: "npm:^6.0.0"
|
||||
signal-exit: "npm:^4.1.0"
|
||||
strip-final-newline: "npm:^3.0.0"
|
||||
checksum: 10c0/2c52d8775f5bf103ce8eec9c7ab3059909ba350a5164744e9947ed14a53f51687c040a250bda833f906d1283aa8803975b84e6c8f7a7c42f99dc8ef80250d1af
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"execa@npm:^9.6.0":
|
||||
version: 9.6.0
|
||||
resolution: "execa@npm:9.6.0"
|
||||
|
|
@ -3531,6 +3591,20 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fkill@npm:^9.0.0":
|
||||
version: 9.0.0
|
||||
resolution: "fkill@npm:9.0.0"
|
||||
dependencies:
|
||||
aggregate-error: "npm:^5.0.0"
|
||||
execa: "npm:^8.0.1"
|
||||
pid-port: "npm:^1.0.0"
|
||||
process-exists: "npm:^5.0.0"
|
||||
ps-list: "npm:^8.1.1"
|
||||
taskkill: "npm:^5.0.0"
|
||||
checksum: 10c0/91090ef26287b833d810789ef828b0fb37458677ea9c7b41a294e20a1147692d80e8ba61fa5a943211d1a0fa9331503f634f30a0f931a794c5be6a635296e90b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"flat-cache@npm:^4.0.0":
|
||||
version: 4.0.1
|
||||
resolution: "flat-cache@npm:4.0.1"
|
||||
|
|
@ -3619,6 +3693,7 @@ __metadata:
|
|||
eslint-plugin-react-refresh: "npm:^0.4.20"
|
||||
eslint-plugin-sonarjs: "npm:^3.0.5"
|
||||
execa: "npm:^9.6.0"
|
||||
fkill: "npm:^9.0.0"
|
||||
globals: "npm:^16.3.0"
|
||||
got: "npm:^14.4.7"
|
||||
husky: "npm:^9.1.7"
|
||||
|
|
@ -3827,6 +3902,20 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"get-stream@npm:^6.0.1":
|
||||
version: 6.0.1
|
||||
resolution: "get-stream@npm:6.0.1"
|
||||
checksum: 10c0/49825d57d3fd6964228e6200a58169464b8e8970489b3acdc24906c782fb7f01f9f56f8e6653c4a50713771d6658f7cfe051e5eb8c12e334138c9c918b296341
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"get-stream@npm:^8.0.1":
|
||||
version: 8.0.1
|
||||
resolution: "get-stream@npm:8.0.1"
|
||||
checksum: 10c0/5c2181e98202b9dae0bb4a849979291043e5892eb40312b47f0c22b9414fc9b28a3b6063d2375705eb24abc41ecf97894d9a51f64ff021511b504477b27b4290
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"get-stream@npm:^9.0.0, get-stream@npm:^9.0.1":
|
||||
version: 9.0.1
|
||||
resolution: "get-stream@npm:9.0.1"
|
||||
|
|
@ -4140,6 +4229,20 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"human-signals@npm:^3.0.1":
|
||||
version: 3.0.1
|
||||
resolution: "human-signals@npm:3.0.1"
|
||||
checksum: 10c0/0bb27e72aea1666322f69ab9816e05df952ef2160346f2293f98f45d472edb1b62d0f1a596697b50d48d8f8222e6db3b9f9dc0b6bf6113866121001f0a8e48e9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"human-signals@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "human-signals@npm:5.0.0"
|
||||
checksum: 10c0/5a9359073fe17a8b58e5a085e9a39a950366d9f00217c4ff5878bd312e09d80f460536ea6a3f260b5943a01fe55c158d1cea3fc7bee3d0520aeef04f6d915c82
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"human-signals@npm:^8.0.1":
|
||||
version: 8.0.1
|
||||
resolution: "human-signals@npm:8.0.1"
|
||||
|
|
@ -4229,6 +4332,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"indent-string@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "indent-string@npm:5.0.0"
|
||||
checksum: 10c0/8ee77b57d92e71745e133f6f444d6fa3ed503ad0e1bcd7e80c8da08b42375c07117128d670589725ed07b1978065803fa86318c309ba45415b7fe13e7f170220
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"infer-owner@npm:^1.0.4":
|
||||
version: 1.0.4
|
||||
resolution: "infer-owner@npm:1.0.4"
|
||||
|
|
@ -4514,6 +4624,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-stream@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "is-stream@npm:3.0.0"
|
||||
checksum: 10c0/eb2f7127af02ee9aa2a0237b730e47ac2de0d4e76a4a905a50a11557f2339df5765eaea4ceb8029f1efa978586abe776908720bfcb1900c20c6ec5145f6f29d8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-stream@npm:^4.0.1":
|
||||
version: 4.0.1
|
||||
resolution: "is-stream@npm:4.0.1"
|
||||
|
|
@ -4615,9 +4732,9 @@ __metadata:
|
|||
linkType: hard
|
||||
|
||||
"isbinaryfile@npm:^5.0.0":
|
||||
version: 5.0.5
|
||||
resolution: "isbinaryfile@npm:5.0.5"
|
||||
checksum: 10c0/c90e57fec90f44c582e57512d7ffaadaaecd9def1f297f7d2a97469c42cde7dc6aecf9b812a455b83a4a8c7eab455c50afb685d3bf7971ddaed365a7c6c81b78
|
||||
version: 5.0.6
|
||||
resolution: "isbinaryfile@npm:5.0.6"
|
||||
checksum: 10c0/53d700189a2a97965f2dcc9ced47644daad24b68ca42eb8a170f0cc0f2b83d52d58d956d969f0dd5f0d4f0012dc488dba079cacd7086c595932897da71085a7f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
@ -5071,6 +5188,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"merge-stream@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "merge-stream@npm:2.0.0"
|
||||
checksum: 10c0/867fdbb30a6d58b011449b8885601ec1690c3e41c759ecd5a9d609094f7aed0096c37823ff4a7190ef0b8f22cc86beb7049196ff68c016e3b3c671d0dac91ce5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"merge2@npm:^1.3.0":
|
||||
version: 1.4.1
|
||||
resolution: "merge2@npm:1.4.1"
|
||||
|
|
@ -5120,6 +5244,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"mimic-fn@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "mimic-fn@npm:4.0.0"
|
||||
checksum: 10c0/de9cc32be9996fd941e512248338e43407f63f6d497abe8441fa33447d922e927de54d4cc3c1a3c6d652857acd770389d5a3823f311a744132760ce2be15ccbf
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"mimic-function@npm:^5.0.0":
|
||||
version: 5.0.1
|
||||
resolution: "mimic-function@npm:5.0.1"
|
||||
|
|
@ -5467,6 +5598,15 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"npm-run-path@npm:^5.1.0":
|
||||
version: 5.3.0
|
||||
resolution: "npm-run-path@npm:5.3.0"
|
||||
dependencies:
|
||||
path-key: "npm:^4.0.0"
|
||||
checksum: 10c0/124df74820c40c2eb9a8612a254ea1d557ddfab1581c3e751f825e3e366d9f00b0d76a3c94ecd8398e7f3eee193018622677e95816e8491f0797b21e30b2deba
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"npm-run-path@npm:^6.0.0":
|
||||
version: 6.0.0
|
||||
resolution: "npm-run-path@npm:6.0.0"
|
||||
|
|
@ -5593,6 +5733,15 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"onetime@npm:^6.0.0":
|
||||
version: 6.0.0
|
||||
resolution: "onetime@npm:6.0.0"
|
||||
dependencies:
|
||||
mimic-fn: "npm:^4.0.0"
|
||||
checksum: 10c0/4eef7c6abfef697dd4479345a4100c382d73c149d2d56170a54a07418c50816937ad09500e1ed1e79d235989d073a9bade8557122aee24f0576ecde0f392bb6c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"onetime@npm:^7.0.0":
|
||||
version: 7.0.0
|
||||
resolution: "onetime@npm:7.0.0"
|
||||
|
|
@ -5806,6 +5955,15 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pid-port@npm:^1.0.0":
|
||||
version: 1.0.2
|
||||
resolution: "pid-port@npm:1.0.2"
|
||||
dependencies:
|
||||
execa: "npm:^8.0.1"
|
||||
checksum: 10c0/b3a62fde1c73f896a0950f5446b014b55fff2bd84068bce3cba4a459561f695de76c11f74a6c54a64f395b0eca29c79f5c3d705061067d3d157ae8115b00e8a3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pidtree@npm:^0.6.0":
|
||||
version: 0.6.0
|
||||
resolution: "pidtree@npm:0.6.0"
|
||||
|
|
@ -5883,6 +6041,15 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"process-exists@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "process-exists@npm:5.0.0"
|
||||
dependencies:
|
||||
ps-list: "npm:^8.0.0"
|
||||
checksum: 10c0/2d0edce88dbf22adc1d30aac08a996bd747786cb91c861b003127345f27217e89c528c2c661404e2ad3d50c2a598e5ae4bbb997ce3628e095a07596590a29b67
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"progress@npm:^2.0.3":
|
||||
version: 2.0.3
|
||||
resolution: "progress@npm:2.0.3"
|
||||
|
|
@ -5918,6 +6085,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ps-list@npm:^8.0.0, ps-list@npm:^8.1.1":
|
||||
version: 8.1.1
|
||||
resolution: "ps-list@npm:8.1.1"
|
||||
checksum: 10c0/5c0fa265f910e03eac14e0785f106d481c4bc3eb95f6fd77a055c231194a5accdea5e1e410050bdb5976cf1620a0bcf41923a10c9540e3ad1b9c8e0bd402116b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pump@npm:^3.0.0":
|
||||
version: 3.0.3
|
||||
resolution: "pump@npm:3.0.3"
|
||||
|
|
@ -6631,7 +6805,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"signal-exit@npm:^3.0.2":
|
||||
"signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.7":
|
||||
version: 3.0.7
|
||||
resolution: "signal-exit@npm:3.0.7"
|
||||
checksum: 10c0/25d272fa73e146048565e08f3309d5b942c1979a6f4a58a8c59d5fa299728e9c2fcd1a759ec870863b1fd38653670240cd420dad2ad9330c71f36608a6a1c912
|
||||
|
|
@ -6956,6 +7130,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"strip-final-newline@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "strip-final-newline@npm:3.0.0"
|
||||
checksum: 10c0/a771a17901427bac6293fd416db7577e2bc1c34a19d38351e9d5478c3c415f523f391003b42ed475f27e33a78233035df183525395f731d3bfb8cdcbd4da08ce
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"strip-final-newline@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "strip-final-newline@npm:4.0.0"
|
||||
|
|
@ -7040,6 +7221,15 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"taskkill@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "taskkill@npm:5.0.0"
|
||||
dependencies:
|
||||
execa: "npm:^6.1.0"
|
||||
checksum: 10c0/ba9684224806a3b0bbc1898a51125d56aa6fa16f4e9e5dfbb14bc00301151297414966067a28cb5078cb5e7db8161b9b1c95a4969d0846078f7f7582fe633e46
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"temp-file@npm:^3.4.0":
|
||||
version: 3.4.0
|
||||
resolution: "temp-file@npm:3.4.0"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue