mirror of
https://github.com/lone-cloud/gerbil
synced 2026-06-03 09:33:10 -07:00
WIP - figuring out more consistent .embd copying for portable binaries, starting to move away from dep injection to pure functional
This commit is contained in:
parent
804472ac78
commit
e56b581109
11 changed files with 767 additions and 269 deletions
524
assets/kcpp_sdui.embd
Normal file
524
assets/kcpp_sdui.embd
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -1,82 +1,44 @@
|
|||
import { app } from 'electron';
|
||||
|
||||
import { WindowManager } from '@/main/managers/WindowManager';
|
||||
import { ConfigManager } from '@/main/managers/ConfigManager';
|
||||
import { LogManager } from '@/main/managers/LogManager';
|
||||
import { KoboldCppManager } from '@/main/managers/KoboldCppManager';
|
||||
import { SillyTavernManager } from '@/main/managers/SillyTavernManager';
|
||||
import { OpenWebUIManager } from '@/main/managers/OpenWebUIManager';
|
||||
import { HardwareManager } from '@/main/managers/HardwareManager';
|
||||
import { BinaryManager } from '@/main/managers/BinaryManager';
|
||||
import { getWindowManager } from '@/main/managers/WindowManager';
|
||||
import { getConfigManager } from '@/main/managers/ConfigManager';
|
||||
import { getLogManager } from '@/main/managers/LogManager';
|
||||
import { getKoboldCppManager } from '@/main/managers/KoboldCppManager';
|
||||
import { getSillyTavernManager } from '@/main/managers/SillyTavernManager';
|
||||
import { getOpenWebUIManager } from '@/main/managers/OpenWebUIManager';
|
||||
import { getHardwareManager } from '@/main/managers/HardwareManager';
|
||||
import { getBinaryManager } from '@/main/managers/BinaryManager';
|
||||
import { IPCHandlers } from '@/main/ipc';
|
||||
import { ensureDir } from '@/utils/fs';
|
||||
import { getConfigDir } from '@/utils/path';
|
||||
|
||||
export class GerbilApp {
|
||||
private windowManager: WindowManager;
|
||||
private koboldManager: KoboldCppManager;
|
||||
private configManager: ConfigManager;
|
||||
private logManager: LogManager;
|
||||
private sillyTavernManager: SillyTavernManager;
|
||||
private openWebUIManager: OpenWebUIManager;
|
||||
private hardwareManager: HardwareManager;
|
||||
private binaryManager: BinaryManager;
|
||||
private ipcHandlers: IPCHandlers;
|
||||
|
||||
constructor() {
|
||||
this.logManager = new LogManager();
|
||||
this.logManager.setupGlobalErrorHandlers();
|
||||
|
||||
this.configManager = new ConfigManager(getConfigDir(), this.logManager);
|
||||
this.windowManager = new WindowManager();
|
||||
this.hardwareManager = new HardwareManager(this.logManager);
|
||||
|
||||
this.koboldManager = new KoboldCppManager(
|
||||
this.configManager,
|
||||
this.windowManager,
|
||||
this.logManager
|
||||
);
|
||||
|
||||
this.binaryManager = new BinaryManager(
|
||||
this.logManager,
|
||||
this.koboldManager,
|
||||
this.hardwareManager
|
||||
);
|
||||
|
||||
this.sillyTavernManager = new SillyTavernManager(
|
||||
this.logManager,
|
||||
this.windowManager
|
||||
);
|
||||
|
||||
this.openWebUIManager = new OpenWebUIManager(
|
||||
this.configManager,
|
||||
this.logManager,
|
||||
this.windowManager
|
||||
);
|
||||
|
||||
this.ipcHandlers = new IPCHandlers(
|
||||
this.koboldManager,
|
||||
this.configManager,
|
||||
this.hardwareManager,
|
||||
this.binaryManager,
|
||||
this.logManager,
|
||||
this.sillyTavernManager,
|
||||
this.openWebUIManager,
|
||||
this.windowManager
|
||||
getKoboldCppManager(),
|
||||
getConfigManager(getConfigDir()),
|
||||
getHardwareManager(),
|
||||
getBinaryManager(),
|
||||
getLogManager(),
|
||||
getSillyTavernManager(),
|
||||
getOpenWebUIManager(),
|
||||
getWindowManager()
|
||||
);
|
||||
}
|
||||
|
||||
private async ensureInstallDirectory(): Promise<void> {
|
||||
const installDir = this.configManager.getInstallDir();
|
||||
const installDir = getConfigManager().getInstallDir();
|
||||
await ensureDir(installDir);
|
||||
}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
await app.whenReady();
|
||||
await this.configManager.initialize();
|
||||
await getConfigManager().initialize();
|
||||
await this.ensureInstallDirectory();
|
||||
|
||||
this.windowManager.createMainWindow();
|
||||
getWindowManager().createMainWindow();
|
||||
this.ipcHandlers.setupHandlers();
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
|
|
@ -92,9 +54,9 @@ export class GerbilApp {
|
|||
|
||||
try {
|
||||
const cleanupPromises = [
|
||||
this.koboldManager.cleanup(),
|
||||
this.sillyTavernManager.cleanup(),
|
||||
this.openWebUIManager.cleanup(),
|
||||
getKoboldCppManager().cleanup(),
|
||||
getSillyTavernManager().cleanup(),
|
||||
getOpenWebUIManager().cleanup(),
|
||||
];
|
||||
|
||||
const timeoutPromise = new Promise<void>((resolve) => {
|
||||
|
|
@ -105,10 +67,10 @@ export class GerbilApp {
|
|||
|
||||
await Promise.race([Promise.all(cleanupPromises), timeoutPromise]);
|
||||
} catch (error) {
|
||||
this.logManager.logError('Error during cleanup:', error as Error);
|
||||
getLogManager().logError('Error during cleanup:', error as Error);
|
||||
}
|
||||
|
||||
this.windowManager.cleanup();
|
||||
getWindowManager().cleanup();
|
||||
|
||||
app.exit(0);
|
||||
});
|
||||
|
|
@ -119,8 +81,8 @@ export class GerbilApp {
|
|||
});
|
||||
|
||||
app.on('activate', () => {
|
||||
if (!this.windowManager.getMainWindow()) {
|
||||
this.windowManager.createMainWindow();
|
||||
if (!getWindowManager().getMainWindow()) {
|
||||
getWindowManager().createMainWindow();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,13 +126,16 @@ export class IPCHandlers {
|
|||
);
|
||||
|
||||
ipcMain.handle('kobold:detectBackendSupport', () =>
|
||||
this.binaryManager.detectBackendSupport()
|
||||
this.binaryManager.detectBackendSupport(this.koboldManager)
|
||||
);
|
||||
|
||||
ipcMain.handle(
|
||||
'kobold:getAvailableBackends',
|
||||
(_, includeDisabled = false) =>
|
||||
this.binaryManager.getAvailableBackends(includeDisabled)
|
||||
this.binaryManager.getAvailableBackends(
|
||||
this.koboldManager,
|
||||
includeDisabled
|
||||
)
|
||||
);
|
||||
|
||||
ipcMain.handle('kobold:getPlatform', () => process.platform);
|
||||
|
|
|
|||
|
|
@ -1,26 +1,13 @@
|
|||
import { join, dirname } from 'path';
|
||||
import { pathExists } from '@/utils/fs';
|
||||
import { LogManager } from '@/main/managers/LogManager';
|
||||
import type { KoboldCppManager } from '@/main/managers/KoboldCppManager';
|
||||
import type { HardwareManager } from '@/main/managers/HardwareManager';
|
||||
import { getLogManager } from '@/main/managers/LogManager';
|
||||
import { getHardwareManager } from '@/main/managers/HardwareManager';
|
||||
import type { BackendOption, BackendSupport } from '@/types';
|
||||
import type { KoboldCppManager } from '@/main/managers/KoboldCppManager';
|
||||
|
||||
export class BinaryManager {
|
||||
private backendSupportCache = new Map<string, BackendSupport>();
|
||||
private availableBackendsCache = new Map<string, BackendOption[]>();
|
||||
private logManager: LogManager;
|
||||
private koboldManager: KoboldCppManager;
|
||||
private hardwareManager: HardwareManager;
|
||||
|
||||
constructor(
|
||||
logManager: LogManager,
|
||||
koboldManager: KoboldCppManager,
|
||||
hardwareManager: HardwareManager
|
||||
) {
|
||||
this.logManager = logManager;
|
||||
this.koboldManager = koboldManager;
|
||||
this.hardwareManager = hardwareManager;
|
||||
}
|
||||
|
||||
private async detectBackendSupportFromPath(
|
||||
koboldBinaryPath: string
|
||||
|
|
@ -68,7 +55,7 @@ export class BinaryManager {
|
|||
support.failsafe = await hasKoboldCppLib('koboldcpp_failsafe');
|
||||
support.cuda = await hasKoboldCppLib('koboldcpp_cublas');
|
||||
} catch (error) {
|
||||
this.logManager.logError(
|
||||
getLogManager().logError(
|
||||
'Error detecting backend support:',
|
||||
error as Error
|
||||
);
|
||||
|
|
@ -78,9 +65,11 @@ export class BinaryManager {
|
|||
return support;
|
||||
}
|
||||
|
||||
async detectBackendSupport(): Promise<BackendSupport | null> {
|
||||
async detectBackendSupport(
|
||||
koboldManager: KoboldCppManager
|
||||
): Promise<BackendSupport | null> {
|
||||
try {
|
||||
const currentBinaryInfo = await this.koboldManager.getCurrentBinaryInfo();
|
||||
const currentBinaryInfo = await koboldManager.getCurrentBinaryInfo();
|
||||
|
||||
if (!currentBinaryInfo?.path) {
|
||||
return null;
|
||||
|
|
@ -88,7 +77,7 @@ export class BinaryManager {
|
|||
|
||||
return this.detectBackendSupportFromPath(currentBinaryInfo.path);
|
||||
} catch (error) {
|
||||
this.logManager.logError(
|
||||
getLogManager().logError(
|
||||
'Error detecting current binary backend support:',
|
||||
error as Error
|
||||
);
|
||||
|
|
@ -98,16 +87,16 @@ export class BinaryManager {
|
|||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
async getAvailableBackends(
|
||||
koboldManager: KoboldCppManager,
|
||||
includeDisabled = false
|
||||
): Promise<BackendOption[]> {
|
||||
try {
|
||||
const hardwareManager = getHardwareManager();
|
||||
const [currentBinaryInfo, hardwareCapabilities, cpuCapabilities] =
|
||||
await Promise.all([
|
||||
this.koboldManager.getCurrentBinaryInfo(),
|
||||
this.hardwareManager.detectGPUCapabilities(),
|
||||
includeDisabled
|
||||
? this.hardwareManager.detectCPU()
|
||||
: Promise.resolve(null),
|
||||
koboldManager.getCurrentBinaryInfo(),
|
||||
hardwareManager.detectGPUCapabilities(),
|
||||
includeDisabled ? hardwareManager.detectCPU() : Promise.resolve(null),
|
||||
]);
|
||||
|
||||
if (!currentBinaryInfo?.path) {
|
||||
|
|
@ -120,7 +109,7 @@ export class BinaryManager {
|
|||
return this.availableBackendsCache.get(cacheKey)!;
|
||||
}
|
||||
|
||||
const backendSupport = await this.detectBackendSupport();
|
||||
const backendSupport = await this.detectBackendSupport(koboldManager);
|
||||
|
||||
if (!backendSupport) {
|
||||
return [];
|
||||
|
|
@ -193,7 +182,7 @@ export class BinaryManager {
|
|||
this.availableBackendsCache.set(cacheKey, backends);
|
||||
return backends;
|
||||
} catch (error) {
|
||||
this.logManager.logError(
|
||||
getLogManager().logError(
|
||||
'Failed to get available backends:',
|
||||
error as Error
|
||||
);
|
||||
|
|
@ -206,3 +195,12 @@ export class BinaryManager {
|
|||
this.availableBackendsCache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
let binaryManagerInstance: BinaryManager;
|
||||
|
||||
export function getBinaryManager(): BinaryManager {
|
||||
if (!binaryManagerInstance) {
|
||||
binaryManagerInstance = new BinaryManager();
|
||||
}
|
||||
return binaryManagerInstance;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { LogManager } from '@/main/managers/LogManager';
|
||||
import { getLogManager } from '@/main/managers/LogManager';
|
||||
import { readJsonFile, writeJsonFile } from '@/utils/fs';
|
||||
import type { FrontendPreference } from '@/types';
|
||||
import { homedir } from 'os';
|
||||
|
|
@ -15,14 +15,14 @@ interface AppConfig {
|
|||
[key: string]: ConfigValue;
|
||||
}
|
||||
|
||||
let configManagerInstance: ConfigManager | null = null;
|
||||
|
||||
export class ConfigManager {
|
||||
private config: AppConfig = {};
|
||||
private configPath: string;
|
||||
private logManager: LogManager;
|
||||
|
||||
constructor(configPath: string, logManager: LogManager) {
|
||||
constructor(configPath: string) {
|
||||
this.configPath = configPath;
|
||||
this.logManager = logManager;
|
||||
}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
|
|
@ -34,7 +34,7 @@ export class ConfigManager {
|
|||
const config = await readJsonFile<AppConfig>(this.configPath);
|
||||
return config || {};
|
||||
} catch (error) {
|
||||
this.logManager.logError('Error loading config:', error as Error);
|
||||
getLogManager().logError('Error loading config:', error as Error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
|
@ -43,7 +43,7 @@ export class ConfigManager {
|
|||
try {
|
||||
await writeJsonFile(this.configPath, this.config);
|
||||
} catch (error) {
|
||||
this.logManager.logError('Error saving config:', error as Error);
|
||||
getLogManager().logError('Error saving config:', error as Error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -98,3 +98,15 @@ export class ConfigManager {
|
|||
await this.saveConfig();
|
||||
}
|
||||
}
|
||||
|
||||
export const getConfigManager = (configPath?: string): ConfigManager => {
|
||||
if (!configManagerInstance) {
|
||||
if (!configPath) {
|
||||
throw new Error(
|
||||
'ConfigManager not initialized. Provide configPath on first call.'
|
||||
);
|
||||
}
|
||||
configManagerInstance = new ConfigManager(configPath);
|
||||
}
|
||||
return configManagerInstance;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/* eslint-disable no-comments/disallowComments */
|
||||
import si from 'systeminformation';
|
||||
import { shortenDeviceName } from '@/utils/hardware';
|
||||
import { LogManager } from '@/main/managers/LogManager';
|
||||
import { getLogManager } from '@/main/managers/LogManager';
|
||||
import { terminateProcess } from '@/utils/process';
|
||||
import type {
|
||||
CPUCapabilities,
|
||||
|
|
@ -12,16 +12,13 @@ import type {
|
|||
} from '@/types/hardware';
|
||||
import { spawn } from 'child_process';
|
||||
|
||||
let hardwareManagerInstance: HardwareManager | null = null;
|
||||
|
||||
export class HardwareManager {
|
||||
private cpuCapabilitiesCache: CPUCapabilities | null = null;
|
||||
private basicGPUInfoCache: BasicGPUInfo | null = null;
|
||||
private gpuCapabilitiesCache: GPUCapabilities | null = null;
|
||||
private gpuMemoryInfoCache: GPUMemoryInfo[] | null = null;
|
||||
private logManager: LogManager;
|
||||
|
||||
constructor(logManager: LogManager) {
|
||||
this.logManager = logManager;
|
||||
}
|
||||
|
||||
async detectCPU(): Promise<CPUCapabilities> {
|
||||
if (this.cpuCapabilitiesCache) {
|
||||
|
|
@ -47,7 +44,7 @@ export class HardwareManager {
|
|||
|
||||
return this.cpuCapabilitiesCache;
|
||||
} catch (error) {
|
||||
this.logManager.logError('CPU detection failed:', error as Error);
|
||||
getLogManager().logError('CPU detection failed:', error as Error);
|
||||
const fallbackCapabilities = {
|
||||
avx: false,
|
||||
avx2: false,
|
||||
|
|
@ -107,7 +104,7 @@ export class HardwareManager {
|
|||
|
||||
return this.basicGPUInfoCache;
|
||||
} catch (error) {
|
||||
this.logManager.logError('GPU detection failed:', error as Error);
|
||||
getLogManager().logError('GPU detection failed:', error as Error);
|
||||
const fallbackGPUInfo = {
|
||||
hasAMD: false,
|
||||
hasNVIDIA: false,
|
||||
|
|
@ -482,10 +479,17 @@ export class HardwareManager {
|
|||
|
||||
this.gpuMemoryInfoCache = memoryInfo;
|
||||
} catch (error) {
|
||||
this.logManager.logError('GPU memory detection failed:', error as Error);
|
||||
getLogManager().logError('GPU memory detection failed:', error as Error);
|
||||
this.gpuMemoryInfoCache = [];
|
||||
}
|
||||
|
||||
return this.gpuMemoryInfoCache;
|
||||
}
|
||||
}
|
||||
|
||||
export const getHardwareManager = (): HardwareManager => {
|
||||
if (!hardwareManagerInstance) {
|
||||
hardwareManagerInstance = new HardwareManager();
|
||||
}
|
||||
return hardwareManagerInstance;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ import axios from 'axios';
|
|||
|
||||
import { execa } from 'execa';
|
||||
import { terminateProcess } from '@/utils/process';
|
||||
import { ConfigManager } from '@/main/managers/ConfigManager';
|
||||
import { LogManager } from '@/main/managers/LogManager';
|
||||
import { WindowManager } from '@/main/managers/WindowManager';
|
||||
import { getConfigManager } from '@/main/managers/ConfigManager';
|
||||
import { getLogManager } from '@/main/managers/LogManager';
|
||||
import { getWindowManager } from '@/main/managers/WindowManager';
|
||||
import { PRODUCT_NAME, SERVER_READY_SIGNALS } from '@/constants';
|
||||
import {
|
||||
KLITE_CSS_OVERRIDE,
|
||||
|
|
@ -39,19 +39,6 @@ import type { FrontendPreference } from '@/types';
|
|||
|
||||
export class KoboldCppManager {
|
||||
private koboldProcess: ChildProcess | null = null;
|
||||
private configManager: ConfigManager;
|
||||
private logManager: LogManager;
|
||||
private windowManager: WindowManager;
|
||||
|
||||
constructor(
|
||||
configManager: ConfigManager,
|
||||
windowManager: WindowManager,
|
||||
logManager: LogManager
|
||||
) {
|
||||
this.configManager = configManager;
|
||||
this.logManager = logManager;
|
||||
this.windowManager = windowManager;
|
||||
}
|
||||
|
||||
private async removeDirectoryWithRetry(
|
||||
dirPath: string,
|
||||
|
|
@ -72,7 +59,7 @@ export class KoboldCppManager {
|
|||
}
|
||||
|
||||
if (isPermissionError && process.platform === 'win32') {
|
||||
this.windowManager.sendKoboldOutput(
|
||||
getWindowManager().sendKoboldOutput(
|
||||
`Attempt ${attempt}/${maxRetries} failed (file in use), retrying in ${delayMs}ms...`
|
||||
);
|
||||
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
||||
|
|
@ -94,7 +81,7 @@ export class KoboldCppManager {
|
|||
|
||||
try {
|
||||
if (this.koboldProcess && !this.koboldProcess.killed) {
|
||||
this.windowManager.sendKoboldOutput(
|
||||
getWindowManager().sendKoboldOutput(
|
||||
'Stopping process before update...'
|
||||
);
|
||||
await this.cleanup();
|
||||
|
|
@ -103,7 +90,7 @@ export class KoboldCppManager {
|
|||
|
||||
await this.removeDirectoryWithRetry(unpackedDirPath);
|
||||
} catch (error) {
|
||||
this.logManager.logError(
|
||||
getLogManager().logError(
|
||||
'Failed to remove existing directory for update:',
|
||||
error as Error
|
||||
);
|
||||
|
|
@ -136,7 +123,7 @@ export class KoboldCppManager {
|
|||
downloadedBytes += chunk.length;
|
||||
if (totalBytes > 0) {
|
||||
const progress = (downloadedBytes / totalBytes) * 100;
|
||||
const mainWindow = this.windowManager.getMainWindow();
|
||||
const mainWindow = getWindowManager().getMainWindow();
|
||||
if (mainWindow) {
|
||||
mainWindow.webContents.send('download-progress', progress);
|
||||
}
|
||||
|
|
@ -151,7 +138,7 @@ export class KoboldCppManager {
|
|||
try {
|
||||
await chmod(tempPackedFilePath, 0o755);
|
||||
} catch (error) {
|
||||
this.logManager.logError(
|
||||
getLogManager().logError(
|
||||
'Failed to make binary executable:',
|
||||
error as Error
|
||||
);
|
||||
|
|
@ -182,7 +169,7 @@ export class KoboldCppManager {
|
|||
await rename(tempPackedFilePath, newLauncherPath);
|
||||
launcherPath = newLauncherPath;
|
||||
} catch (error) {
|
||||
this.logManager.logError(
|
||||
getLogManager().logError(
|
||||
'Failed to rename binary as launcher:',
|
||||
error as Error
|
||||
);
|
||||
|
|
@ -192,7 +179,7 @@ export class KoboldCppManager {
|
|||
try {
|
||||
await unlink(tempPackedFilePath);
|
||||
} catch (error) {
|
||||
this.logManager.logError(
|
||||
getLogManager().logError(
|
||||
'Failed to cleanup packed file:',
|
||||
error as Error
|
||||
);
|
||||
|
|
@ -208,7 +195,7 @@ export class KoboldCppManager {
|
|||
|
||||
async downloadRelease(asset: GitHubAsset): Promise<string> {
|
||||
const tempPackedFilePath = join(
|
||||
this.configManager.getInstallDir(),
|
||||
getConfigManager().getInstallDir(),
|
||||
`${asset.name}.packed`
|
||||
);
|
||||
const baseFilename = stripAssetExtensions(asset.name);
|
||||
|
|
@ -216,7 +203,7 @@ export class KoboldCppManager {
|
|||
? `${baseFilename}-${asset.version}`
|
||||
: baseFilename;
|
||||
const unpackedDirPath = join(
|
||||
this.configManager.getInstallDir(),
|
||||
getConfigManager().getInstallDir(),
|
||||
folderName
|
||||
);
|
||||
|
||||
|
|
@ -233,15 +220,15 @@ export class KoboldCppManager {
|
|||
unpackedDirPath
|
||||
);
|
||||
|
||||
const currentBinary = this.configManager.getCurrentKoboldBinary();
|
||||
const currentBinary = getConfigManager().getCurrentKoboldBinary();
|
||||
if (!currentBinary || (asset.isUpdate && asset.wasCurrentBinary)) {
|
||||
await this.configManager.setCurrentKoboldBinary(launcherPath);
|
||||
await getConfigManager().setCurrentKoboldBinary(launcherPath);
|
||||
}
|
||||
|
||||
this.windowManager.sendToRenderer('versions-updated');
|
||||
getWindowManager().sendToRenderer('versions-updated');
|
||||
return launcherPath;
|
||||
} catch (error) {
|
||||
this.logManager.logError(
|
||||
getLogManager().logError(
|
||||
'Failed to download or unpack binary:',
|
||||
error as Error
|
||||
);
|
||||
|
|
@ -318,7 +305,7 @@ export class KoboldCppManager {
|
|||
await writeFile(kliteEmbdPath, patchedContent, 'utf8');
|
||||
}
|
||||
} catch (error) {
|
||||
this.logManager.logError('Failed to patch klite.embd:', error as Error);
|
||||
getLogManager().logError('Failed to patch klite.embd:', error as Error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -338,7 +325,7 @@ export class KoboldCppManager {
|
|||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.logManager.logError(
|
||||
getLogManager().logError(
|
||||
'Failed to patch kcpp_sdui.embd:',
|
||||
error as Error
|
||||
);
|
||||
|
|
@ -361,7 +348,7 @@ export class KoboldCppManager {
|
|||
|
||||
async getInstalledVersions(): Promise<InstalledVersion[]> {
|
||||
try {
|
||||
const installDir = this.configManager.getInstallDir();
|
||||
const installDir = getConfigManager().getInstallDir();
|
||||
if (!(await pathExists(installDir))) {
|
||||
return [];
|
||||
}
|
||||
|
|
@ -401,7 +388,7 @@ export class KoboldCppManager {
|
|||
size: launcher.size,
|
||||
} as InstalledVersion;
|
||||
} catch (error) {
|
||||
this.logManager.logError(
|
||||
getLogManager().logError(
|
||||
`Could not detect version for ${launcher.filename}:`,
|
||||
error as Error
|
||||
);
|
||||
|
|
@ -414,7 +401,7 @@ export class KoboldCppManager {
|
|||
(version): version is InstalledVersion => version !== null
|
||||
);
|
||||
} catch (error) {
|
||||
this.logManager.logError(
|
||||
getLogManager().logError(
|
||||
'Error scanning install directory:',
|
||||
error as Error
|
||||
);
|
||||
|
|
@ -428,7 +415,7 @@ export class KoboldCppManager {
|
|||
const configFiles: { name: string; path: string; size: number }[] = [];
|
||||
|
||||
try {
|
||||
const installDir = this.configManager.getInstallDir();
|
||||
const installDir = getConfigManager().getInstallDir();
|
||||
if (await pathExists(installDir)) {
|
||||
const files = await readdir(installDir);
|
||||
|
||||
|
|
@ -451,7 +438,7 @@ export class KoboldCppManager {
|
|||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.logManager.logError(
|
||||
getLogManager().logError(
|
||||
'Error scanning for config files:',
|
||||
error as Error
|
||||
);
|
||||
|
|
@ -469,7 +456,7 @@ export class KoboldCppManager {
|
|||
const config = await readJsonFile(filePath);
|
||||
return config as KoboldConfig;
|
||||
} catch (error) {
|
||||
this.logManager.logError('Error parsing config file:', error as Error);
|
||||
getLogManager().logError('Error parsing config file:', error as Error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -479,19 +466,19 @@ export class KoboldCppManager {
|
|||
configData: KoboldConfig
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const installDir = this.configManager.getInstallDir();
|
||||
const installDir = getConfigManager().getInstallDir();
|
||||
const configPath = join(installDir, configFileName);
|
||||
await writeJsonFile(configPath, configData);
|
||||
return true;
|
||||
} catch (error) {
|
||||
this.logManager.logError('Error saving config file:', error as Error);
|
||||
getLogManager().logError('Error saving config file:', error as Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async selectModelFile(title = 'Select Model File'): Promise<string | null> {
|
||||
try {
|
||||
const mainWindow = this.windowManager.getMainWindow();
|
||||
const mainWindow = getWindowManager().getMainWindow();
|
||||
if (!mainWindow) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -514,13 +501,13 @@ export class KoboldCppManager {
|
|||
|
||||
return result.filePaths[0];
|
||||
} catch (error) {
|
||||
this.logManager.logError('Error selecting model file:', error as Error);
|
||||
getLogManager().logError('Error selecting model file:', error as Error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async getCurrentVersion(): Promise<InstalledVersion | null> {
|
||||
const currentBinaryPath = this.configManager.getCurrentKoboldBinary();
|
||||
const currentBinaryPath = getConfigManager().getCurrentKoboldBinary();
|
||||
const versions = await this.getInstalledVersions();
|
||||
|
||||
if (currentBinaryPath && (await pathExists(currentBinaryPath))) {
|
||||
|
|
@ -532,12 +519,12 @@ export class KoboldCppManager {
|
|||
|
||||
const firstVersion = versions[0];
|
||||
if (firstVersion) {
|
||||
await this.configManager.setCurrentKoboldBinary(firstVersion.path);
|
||||
await getConfigManager().setCurrentKoboldBinary(firstVersion.path);
|
||||
return firstVersion;
|
||||
}
|
||||
|
||||
if (currentBinaryPath) {
|
||||
await this.configManager.setCurrentKoboldBinary('');
|
||||
await getConfigManager().setCurrentKoboldBinary('');
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
@ -565,9 +552,9 @@ export class KoboldCppManager {
|
|||
|
||||
async setCurrentVersion(binaryPath: string): Promise<boolean> {
|
||||
if (await pathExists(binaryPath)) {
|
||||
await this.configManager.setCurrentKoboldBinary(binaryPath);
|
||||
await getConfigManager().setCurrentKoboldBinary(binaryPath);
|
||||
|
||||
this.windowManager.sendToRenderer('versions-updated');
|
||||
getWindowManager().sendToRenderer('versions-updated');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -626,14 +613,14 @@ export class KoboldCppManager {
|
|||
const result = await dialog.showOpenDialog({
|
||||
properties: ['openDirectory', 'createDirectory'],
|
||||
title: `Select the ${PRODUCT_NAME} Installation Directory`,
|
||||
defaultPath: this.configManager.getInstallDir(),
|
||||
defaultPath: getConfigManager().getInstallDir(),
|
||||
buttonLabel: 'Select Directory',
|
||||
});
|
||||
|
||||
if (!result.canceled && result.filePaths.length > 0) {
|
||||
await this.configManager.setInstallDir(result.filePaths[0]);
|
||||
await getConfigManager().setInstallDir(result.filePaths[0]);
|
||||
|
||||
this.windowManager.sendToRenderer(
|
||||
getWindowManager().sendToRenderer(
|
||||
'install-dir-changed',
|
||||
result.filePaths[0]
|
||||
);
|
||||
|
|
@ -655,12 +642,12 @@ export class KoboldCppManager {
|
|||
|
||||
const currentVersion = await this.getCurrentVersion();
|
||||
if (!currentVersion || !(await pathExists(currentVersion.path))) {
|
||||
const rawPath = this.configManager.getCurrentKoboldBinary();
|
||||
const rawPath = getConfigManager().getCurrentKoboldBinary();
|
||||
const error = currentVersion
|
||||
? `Binary file does not exist at path: ${currentVersion.path}`
|
||||
: 'No version configured';
|
||||
|
||||
this.logManager.logError(
|
||||
getLogManager().logError(
|
||||
`Launch failed: ${error}. Raw config path: "${rawPath}", Current version: ${JSON.stringify(currentVersion)}`
|
||||
);
|
||||
|
||||
|
|
@ -698,7 +685,7 @@ export class KoboldCppManager {
|
|||
|
||||
const commandLine = `$ ${currentVersion.path} ${finalArgs.join(' ')}`;
|
||||
|
||||
this.windowManager.sendKoboldOutput(commandLine);
|
||||
getWindowManager().sendKoboldOutput(commandLine);
|
||||
|
||||
let readyResolve:
|
||||
| ((value: { success: boolean; pid?: number; error?: string }) => void)
|
||||
|
|
@ -717,7 +704,7 @@ export class KoboldCppManager {
|
|||
|
||||
child.stdout?.on('data', (data) => {
|
||||
const output = data.toString();
|
||||
this.windowManager.sendKoboldOutput(output, true);
|
||||
getWindowManager().sendKoboldOutput(output, true);
|
||||
|
||||
if (!isReady && output.includes(SERVER_READY_SIGNALS.KOBOLDCPP)) {
|
||||
isReady = true;
|
||||
|
|
@ -727,7 +714,7 @@ export class KoboldCppManager {
|
|||
|
||||
child.stderr?.on('data', (data) => {
|
||||
const output = data.toString();
|
||||
this.windowManager.sendKoboldOutput(output, true);
|
||||
getWindowManager().sendKoboldOutput(output, true);
|
||||
|
||||
if (!isReady && output.includes(SERVER_READY_SIGNALS.KOBOLDCPP)) {
|
||||
isReady = true;
|
||||
|
|
@ -743,7 +730,7 @@ export class KoboldCppManager {
|
|||
: code && (code > 1 || code < 0)
|
||||
? `\n[ERROR] Process exited with code ${code}`
|
||||
: `\n[INFO] Process exited with code ${code}`;
|
||||
this.windowManager.sendKoboldOutput(displayMessage);
|
||||
getWindowManager().sendKoboldOutput(displayMessage);
|
||||
this.koboldProcess = null;
|
||||
|
||||
if (!isReady) {
|
||||
|
|
@ -756,9 +743,9 @@ export class KoboldCppManager {
|
|||
});
|
||||
|
||||
child.on('error', (error) => {
|
||||
this.logManager.logError(`Process error: ${error.message}`, error);
|
||||
getLogManager().logError(`Process error: ${error.message}`, error);
|
||||
|
||||
this.windowManager.sendKoboldOutput(
|
||||
getWindowManager().sendKoboldOutput(
|
||||
`\n[ERROR] Process error: ${error.message}\n`
|
||||
);
|
||||
this.koboldProcess = null;
|
||||
|
|
@ -771,7 +758,7 @@ export class KoboldCppManager {
|
|||
return readyPromise;
|
||||
} catch (error) {
|
||||
const errorMessage = (error as Error).message;
|
||||
this.logManager.logError(
|
||||
getLogManager().logError(
|
||||
`Failed to launch: ${errorMessage}`,
|
||||
error as Error
|
||||
);
|
||||
|
|
@ -783,7 +770,7 @@ export class KoboldCppManager {
|
|||
if (this.koboldProcess) {
|
||||
await terminateProcess(this.koboldProcess, {
|
||||
timeoutMs: 5000,
|
||||
logError: (message, error) => this.logManager.logError(message, error),
|
||||
logError: (message, error) => getLogManager().logError(message, error),
|
||||
});
|
||||
this.koboldProcess = null;
|
||||
}
|
||||
|
|
@ -792,9 +779,18 @@ export class KoboldCppManager {
|
|||
async cleanup(): Promise<void> {
|
||||
if (this.koboldProcess) {
|
||||
await terminateProcess(this.koboldProcess, {
|
||||
logError: (message, error) => this.logManager.logError(message, error),
|
||||
logError: (message, error) => getLogManager().logError(message, error),
|
||||
});
|
||||
this.koboldProcess = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let koboldCppManagerInstance: KoboldCppManager;
|
||||
|
||||
export function getKoboldCppManager(): KoboldCppManager {
|
||||
if (!koboldCppManagerInstance) {
|
||||
koboldCppManagerInstance = new KoboldCppManager();
|
||||
}
|
||||
return koboldCppManagerInstance;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import { join } from 'path';
|
|||
import { createLogger, format, type Logger } from 'winston';
|
||||
import DailyRotateFile from 'winston-daily-rotate-file';
|
||||
|
||||
let logManagerInstance: LogManager | null = null;
|
||||
|
||||
export class LogManager {
|
||||
private logger: Logger;
|
||||
|
||||
|
|
@ -86,3 +88,10 @@ export class LogManager {
|
|||
return join(app.getPath('userData'), 'logs');
|
||||
}
|
||||
}
|
||||
|
||||
export const getLogManager = (): LogManager => {
|
||||
if (!logManagerInstance) {
|
||||
logManagerInstance = new LogManager();
|
||||
}
|
||||
return logManagerInstance;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,18 +4,15 @@ import { join } from 'path';
|
|||
import { homedir } from 'os';
|
||||
import { access } from 'fs/promises';
|
||||
|
||||
import { LogManager } from './LogManager';
|
||||
import { WindowManager } from './WindowManager';
|
||||
import { ConfigManager } from './ConfigManager';
|
||||
import { getLogManager } from './LogManager';
|
||||
import { getWindowManager } from './WindowManager';
|
||||
import { getConfigManager } from './ConfigManager';
|
||||
import { OPENWEBUI, SERVER_READY_SIGNALS } from '@/constants';
|
||||
import { terminateProcess } from '@/utils/process';
|
||||
import { parseKoboldConfig } from '@/utils/kobold';
|
||||
|
||||
export class OpenWebUIManager {
|
||||
private openWebUIProcess: ChildProcess | null = null;
|
||||
private logManager: LogManager;
|
||||
private windowManager: WindowManager;
|
||||
private configManager: ConfigManager;
|
||||
private static readonly OPENWEBUI_BASE_ARGS = [
|
||||
'--python',
|
||||
'3.11',
|
||||
|
|
@ -23,15 +20,7 @@ export class OpenWebUIManager {
|
|||
'serve',
|
||||
];
|
||||
|
||||
constructor(
|
||||
configManager: ConfigManager,
|
||||
logManager: LogManager,
|
||||
windowManager: WindowManager
|
||||
) {
|
||||
this.configManager = configManager;
|
||||
this.logManager = logManager;
|
||||
this.windowManager = windowManager;
|
||||
|
||||
constructor() {
|
||||
process.on('SIGINT', () => {
|
||||
this.cleanup().catch(() => {
|
||||
void 0;
|
||||
|
|
@ -126,13 +115,13 @@ export class OpenWebUIManager {
|
|||
}
|
||||
|
||||
private async waitForOpenWebUIToStart(): Promise<void> {
|
||||
this.windowManager.sendKoboldOutput('Waiting for Open WebUI to start...');
|
||||
getWindowManager().sendKoboldOutput('Waiting for Open WebUI to start...');
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const checkForOutput = (data: Buffer) => {
|
||||
const output = data.toString();
|
||||
if (output.includes(SERVER_READY_SIGNALS.OPENWEBUI)) {
|
||||
this.windowManager.sendKoboldOutput('Open WebUI is now running!');
|
||||
getWindowManager().sendKoboldOutput('Open WebUI is now running!');
|
||||
resolve();
|
||||
|
||||
if (this.openWebUIProcess?.stdout) {
|
||||
|
|
@ -167,11 +156,11 @@ export class OpenWebUIManager {
|
|||
|
||||
await this.stopFrontend();
|
||||
|
||||
this.windowManager.sendKoboldOutput(
|
||||
getWindowManager().sendKoboldOutput(
|
||||
`Preparing Open WebUI to connect at ${koboldHost}:${koboldPort}...`
|
||||
);
|
||||
|
||||
this.windowManager.sendKoboldOutput(
|
||||
getWindowManager().sendKoboldOutput(
|
||||
`Starting ${config.name} frontend on port ${config.port}...`
|
||||
);
|
||||
|
||||
|
|
@ -183,9 +172,9 @@ export class OpenWebUIManager {
|
|||
config.port.toString(),
|
||||
];
|
||||
|
||||
this.windowManager.sendKoboldOutput('Starting Open WebUI with uv...');
|
||||
getWindowManager().sendKoboldOutput('Starting Open WebUI with uv...');
|
||||
|
||||
const installDir = this.configManager.getInstallDir();
|
||||
const installDir = getConfigManager().getInstallDir();
|
||||
const openWebUIDataDir = join(installDir, 'openwebui-data');
|
||||
|
||||
this.openWebUIProcess = await this.createUvProcess(openWebUIArgs, {
|
||||
|
|
@ -199,9 +188,9 @@ export class OpenWebUIManager {
|
|||
this.openWebUIProcess.stdout.on('data', (data: Buffer) => {
|
||||
try {
|
||||
const output = data.toString('utf8');
|
||||
this.windowManager.sendKoboldOutput(output, true);
|
||||
getWindowManager().sendKoboldOutput(output, true);
|
||||
} catch (error) {
|
||||
this.logManager.logError(
|
||||
getLogManager().logError(
|
||||
'Error processing stdout data:',
|
||||
error as Error
|
||||
);
|
||||
|
|
@ -213,9 +202,9 @@ export class OpenWebUIManager {
|
|||
this.openWebUIProcess.stderr.on('data', (data: Buffer) => {
|
||||
try {
|
||||
const output = data.toString('utf8');
|
||||
this.windowManager.sendKoboldOutput(output, true);
|
||||
getWindowManager().sendKoboldOutput(output, true);
|
||||
} catch (error) {
|
||||
this.logManager.logError(
|
||||
getLogManager().logError(
|
||||
'Error processing stderr data:',
|
||||
error as Error
|
||||
);
|
||||
|
|
@ -229,14 +218,14 @@ export class OpenWebUIManager {
|
|||
const message = signal
|
||||
? `Open WebUI terminated with signal ${signal}`
|
||||
: `Open WebUI exited with code ${code}`;
|
||||
this.windowManager.sendKoboldOutput(message);
|
||||
getWindowManager().sendKoboldOutput(message);
|
||||
this.openWebUIProcess = null;
|
||||
}
|
||||
);
|
||||
|
||||
this.openWebUIProcess.on('error', (error) => {
|
||||
this.logManager.logError('Open WebUI process error:', error);
|
||||
this.windowManager.sendKoboldOutput(
|
||||
getLogManager().logError('Open WebUI process error:', error);
|
||||
getWindowManager().sendKoboldOutput(
|
||||
`Open WebUI error: ${error.message}`
|
||||
);
|
||||
this.openWebUIProcess = null;
|
||||
|
|
@ -244,21 +233,21 @@ export class OpenWebUIManager {
|
|||
|
||||
await this.waitForOpenWebUIToStart();
|
||||
|
||||
this.windowManager.sendKoboldOutput(
|
||||
getWindowManager().sendKoboldOutput(
|
||||
`Open WebUI is ready and auto-configured!`
|
||||
);
|
||||
this.windowManager.sendKoboldOutput(
|
||||
getWindowManager().sendKoboldOutput(
|
||||
`Access Open WebUI at: http://localhost:${config.port}`
|
||||
);
|
||||
this.windowManager.sendKoboldOutput(
|
||||
getWindowManager().sendKoboldOutput(
|
||||
`Connection: ${koboldUrl}/v1 (auto-configured)`
|
||||
);
|
||||
} catch (error) {
|
||||
this.logManager.logError(
|
||||
getLogManager().logError(
|
||||
'Failed to start Open WebUI frontend:',
|
||||
error as Error
|
||||
);
|
||||
this.windowManager.sendKoboldOutput(
|
||||
getWindowManager().sendKoboldOutput(
|
||||
`Failed to start Open WebUI: ${(error as Error).message}`
|
||||
);
|
||||
this.openWebUIProcess = null;
|
||||
|
|
@ -268,16 +257,16 @@ export class OpenWebUIManager {
|
|||
|
||||
async stopFrontend(): Promise<void> {
|
||||
if (this.openWebUIProcess) {
|
||||
this.windowManager.sendKoboldOutput('Stopping Open WebUI...');
|
||||
getWindowManager().sendKoboldOutput('Stopping Open WebUI...');
|
||||
|
||||
try {
|
||||
await terminateProcess(this.openWebUIProcess, {
|
||||
logError: (message, error) =>
|
||||
this.logManager.logError(message, error),
|
||||
getLogManager().logError(message, error),
|
||||
});
|
||||
this.windowManager.sendKoboldOutput('Open WebUI stopped');
|
||||
getWindowManager().sendKoboldOutput('Open WebUI stopped');
|
||||
} catch (error) {
|
||||
this.logManager.logError('Error stopping Open WebUI:', error as Error);
|
||||
getLogManager().logError('Error stopping Open WebUI:', error as Error);
|
||||
}
|
||||
|
||||
this.openWebUIProcess = null;
|
||||
|
|
@ -288,3 +277,12 @@ export class OpenWebUIManager {
|
|||
await this.stopFrontend();
|
||||
}
|
||||
}
|
||||
|
||||
let openWebUIManagerInstance: OpenWebUIManager;
|
||||
|
||||
export function getOpenWebUIManager(): OpenWebUIManager {
|
||||
if (!openWebUIManagerInstance) {
|
||||
openWebUIManagerInstance = new OpenWebUIManager();
|
||||
}
|
||||
return openWebUIManagerInstance;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ import { join } from 'path';
|
|||
import { access, readdir } from 'fs/promises';
|
||||
import type { ChildProcess } from 'child_process';
|
||||
|
||||
import { LogManager } from './LogManager';
|
||||
import { WindowManager } from './WindowManager';
|
||||
import { getLogManager } from './LogManager';
|
||||
import { getWindowManager } from './WindowManager';
|
||||
import { SILLYTAVERN, SERVER_READY_SIGNALS } from '@/constants';
|
||||
import { terminateProcess } from '@/utils/process';
|
||||
import { pathExists, readJsonFile, writeJsonFile } from '@/utils/fs';
|
||||
|
|
@ -15,8 +15,6 @@ import { parseKoboldConfig } from '@/utils/kobold';
|
|||
export class SillyTavernManager {
|
||||
private sillyTavernProcess: ChildProcess | null = null;
|
||||
private proxyServer: Server | null = null;
|
||||
private logManager: LogManager;
|
||||
private windowManager: WindowManager;
|
||||
private static readonly SILLYTAVERN_BASE_ARGS = [
|
||||
'sillytavern',
|
||||
'--global',
|
||||
|
|
@ -26,10 +24,7 @@ export class SillyTavernManager {
|
|||
'--disableCsrf',
|
||||
];
|
||||
|
||||
constructor(logManager: LogManager, windowManager: WindowManager) {
|
||||
this.logManager = logManager;
|
||||
this.windowManager = windowManager;
|
||||
|
||||
constructor() {
|
||||
process.on('SIGINT', () => {
|
||||
this.cleanup().catch(() => {
|
||||
void 0;
|
||||
|
|
@ -206,13 +201,13 @@ export class SillyTavernManager {
|
|||
const settingsPath = await this.getSillyTavernSettingsPath();
|
||||
|
||||
if (await pathExists(settingsPath)) {
|
||||
this.windowManager.sendKoboldOutput(
|
||||
getWindowManager().sendKoboldOutput(
|
||||
`SillyTavern settings found at ${settingsPath}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.windowManager.sendKoboldOutput(
|
||||
getWindowManager().sendKoboldOutput(
|
||||
'SillyTavern settings not found, starting SillyTavern briefly to generate config...'
|
||||
);
|
||||
|
||||
|
|
@ -224,7 +219,7 @@ export class SillyTavernManager {
|
|||
let hasResolved = false;
|
||||
|
||||
initProcess.on('exit', (code: number | null, signal: string | null) => {
|
||||
this.windowManager.sendKoboldOutput(
|
||||
getWindowManager().sendKoboldOutput(
|
||||
signal
|
||||
? `SillyTavern init process terminated with signal ${signal}`
|
||||
: `SillyTavern init process exited with code ${code}`
|
||||
|
|
@ -239,14 +234,14 @@ export class SillyTavernManager {
|
|||
? '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(
|
||||
getLogManager().logError(
|
||||
'SillyTavern initialization failed:',
|
||||
new Error(errorMsg)
|
||||
);
|
||||
this.windowManager.sendKoboldOutput(`CRITICAL ERROR: ${errorMsg}`);
|
||||
getWindowManager().sendKoboldOutput(`CRITICAL ERROR: ${errorMsg}`);
|
||||
reject(new Error(errorMsg));
|
||||
} else {
|
||||
this.windowManager.sendKoboldOutput(
|
||||
getWindowManager().sendKoboldOutput(
|
||||
'SillyTavern settings should now be generated'
|
||||
);
|
||||
resolve();
|
||||
|
|
@ -257,11 +252,11 @@ export class SillyTavernManager {
|
|||
initProcess.on('error', (error) => {
|
||||
if (!hasResolved) {
|
||||
hasResolved = true;
|
||||
this.logManager.logError(
|
||||
getLogManager().logError(
|
||||
'Failed to initialize SillyTavern settings:',
|
||||
error
|
||||
);
|
||||
this.windowManager.sendKoboldOutput(
|
||||
getWindowManager().sendKoboldOutput(
|
||||
`SillyTavern initialization error: ${error.message}`
|
||||
);
|
||||
reject(error);
|
||||
|
|
@ -279,10 +274,10 @@ export class SillyTavernManager {
|
|||
|
||||
await terminateProcess(initProcess, {
|
||||
logError: (message, error) =>
|
||||
this.logManager.logError(message, error),
|
||||
getLogManager().logError(message, error),
|
||||
});
|
||||
|
||||
this.windowManager.sendKoboldOutput(
|
||||
getWindowManager().sendKoboldOutput(
|
||||
'SillyTavern settings should now be generated'
|
||||
);
|
||||
|
||||
|
|
@ -295,7 +290,7 @@ export class SillyTavernManager {
|
|||
|
||||
if (initProcess.stderr) {
|
||||
initProcess.stderr.on('data', (data: Buffer) => {
|
||||
this.windowManager.sendKoboldOutput(data.toString().trim());
|
||||
getWindowManager().sendKoboldOutput(data.toString().trim());
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -316,12 +311,12 @@ export class SillyTavernManager {
|
|||
await readJsonFile<Record<string, unknown>>(configPath);
|
||||
if (existingSettings) {
|
||||
settings = existingSettings;
|
||||
this.windowManager.sendKoboldOutput(
|
||||
getWindowManager().sendKoboldOutput(
|
||||
`Loaded existing SillyTavern settings`
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
this.windowManager.sendKoboldOutput(
|
||||
getWindowManager().sendKoboldOutput(
|
||||
`Could not read existing settings, creating new ones`
|
||||
);
|
||||
}
|
||||
|
|
@ -334,7 +329,7 @@ export class SillyTavernManager {
|
|||
powerUser.auto_connect = true;
|
||||
|
||||
if (isImageMode) {
|
||||
this.windowManager.sendKoboldOutput(
|
||||
getWindowManager().sendKoboldOutput(
|
||||
`Image generation mode detected. Please configure SillyTavern manually:\n` +
|
||||
`1. Open SillyTavern and navigate to Settings (top-right gear icon)\n` +
|
||||
`2. Go to 'Extensions' tab and enable 'Image Generation'\n` +
|
||||
|
|
@ -358,33 +353,33 @@ export class SillyTavernManager {
|
|||
settings.main_api = 'textgenerationwebui';
|
||||
textgenSettings.type = 'koboldcpp';
|
||||
|
||||
this.windowManager.sendKoboldOutput(
|
||||
getWindowManager().sendKoboldOutput(
|
||||
`Configured SillyTavern for text generation at ${koboldUrl}`
|
||||
);
|
||||
|
||||
await writeJsonFile(configPath, settings);
|
||||
|
||||
this.windowManager.sendKoboldOutput(
|
||||
getWindowManager().sendKoboldOutput(
|
||||
`SillyTavern configuration updated successfully!`
|
||||
);
|
||||
} catch (error) {
|
||||
this.logManager.logError(
|
||||
getLogManager().logError(
|
||||
'Failed to setup SillyTavern config:',
|
||||
error as Error
|
||||
);
|
||||
this.windowManager.sendKoboldOutput(
|
||||
getWindowManager().sendKoboldOutput(
|
||||
`Failed to configure SillyTavern: ${error instanceof Error ? error.message : String(error)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async waitForSillyTavernToStart(_port: number): Promise<void> {
|
||||
this.windowManager.sendKoboldOutput('Waiting for SillyTavern to start...');
|
||||
getWindowManager().sendKoboldOutput('Waiting for SillyTavern to start...');
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const checkForOutput = (data: Buffer) => {
|
||||
if (data.toString().includes(SERVER_READY_SIGNALS.SILLYTAVERN)) {
|
||||
this.windowManager.sendKoboldOutput('SillyTavern is now running!');
|
||||
getWindowManager().sendKoboldOutput('SillyTavern is now running!');
|
||||
resolve();
|
||||
|
||||
if (this.sillyTavernProcess?.stdout) {
|
||||
|
|
@ -422,7 +417,7 @@ export class SillyTavernManager {
|
|||
});
|
||||
|
||||
proxyReq.on('error', (err) => {
|
||||
this.logManager.logError('Proxy request error:', err);
|
||||
getLogManager().logError('Proxy request error:', err);
|
||||
res.writeHead(500);
|
||||
res.end('Proxy error');
|
||||
});
|
||||
|
|
@ -431,13 +426,13 @@ export class SillyTavernManager {
|
|||
});
|
||||
|
||||
this.proxyServer.listen(proxyPort, () => {
|
||||
this.windowManager.sendKoboldOutput(
|
||||
getWindowManager().sendKoboldOutput(
|
||||
`Proxy server started on port ${proxyPort}, forwarding to SillyTavern on port ${targetPort}`
|
||||
);
|
||||
});
|
||||
|
||||
this.proxyServer.on('error', (err) => {
|
||||
this.logManager.logError('Proxy server error:', err);
|
||||
getLogManager().logError('Proxy server error:', err);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -456,14 +451,14 @@ export class SillyTavernManager {
|
|||
|
||||
await this.stopFrontend();
|
||||
|
||||
this.windowManager.sendKoboldOutput(
|
||||
getWindowManager().sendKoboldOutput(
|
||||
`Preparing SillyTavern to connect at ${koboldHost}:${koboldPort}...`
|
||||
);
|
||||
|
||||
await this.ensureSillyTavernSettings();
|
||||
await this.setupSillyTavernConfig(koboldHost, koboldPort, isImageMode);
|
||||
|
||||
this.windowManager.sendKoboldOutput(
|
||||
getWindowManager().sendKoboldOutput(
|
||||
`Starting ${config.name} frontend on port ${config.port}...`
|
||||
);
|
||||
|
||||
|
|
@ -473,7 +468,7 @@ export class SillyTavernManager {
|
|||
config.port.toString(),
|
||||
];
|
||||
|
||||
this.windowManager.sendKoboldOutput(
|
||||
getWindowManager().sendKoboldOutput(
|
||||
'Final port check before starting SillyTavern...'
|
||||
);
|
||||
|
||||
|
|
@ -481,13 +476,13 @@ export class SillyTavernManager {
|
|||
|
||||
if (this.sillyTavernProcess.stdout) {
|
||||
this.sillyTavernProcess.stdout.on('data', (data: Buffer) => {
|
||||
this.windowManager.sendKoboldOutput(data.toString(), true);
|
||||
getWindowManager().sendKoboldOutput(data.toString(), true);
|
||||
});
|
||||
}
|
||||
|
||||
if (this.sillyTavernProcess.stderr) {
|
||||
this.sillyTavernProcess.stderr.on('data', (data: Buffer) => {
|
||||
this.windowManager.sendKoboldOutput(data.toString(), true);
|
||||
getWindowManager().sendKoboldOutput(data.toString(), true);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -497,14 +492,14 @@ export class SillyTavernManager {
|
|||
const message = signal
|
||||
? `SillyTavern terminated with signal ${signal}`
|
||||
: `SillyTavern exited with code ${code}`;
|
||||
this.windowManager.sendKoboldOutput(message);
|
||||
getWindowManager().sendKoboldOutput(message);
|
||||
this.sillyTavernProcess = null;
|
||||
}
|
||||
);
|
||||
|
||||
this.sillyTavernProcess.on('error', (error) => {
|
||||
this.logManager.logError('SillyTavern process error:', error);
|
||||
this.windowManager.sendKoboldOutput(
|
||||
getLogManager().logError('SillyTavern process error:', error);
|
||||
getWindowManager().sendKoboldOutput(
|
||||
`SillyTavern error: ${error.message}`
|
||||
);
|
||||
|
||||
|
|
@ -514,7 +509,7 @@ export class SillyTavernManager {
|
|||
await this.waitForSillyTavernToStart(config.port);
|
||||
this.createProxyServer(config.port, config.proxyPort);
|
||||
} catch (error) {
|
||||
this.logManager.logError(
|
||||
getLogManager().logError(
|
||||
`Failed to start SillyTavern: ${error instanceof Error ? error.message : String(error)}`,
|
||||
error as Error
|
||||
);
|
||||
|
|
@ -529,7 +524,7 @@ export class SillyTavernManager {
|
|||
promises.push(
|
||||
terminateProcess(this.sillyTavernProcess, {
|
||||
logError: (message, error) =>
|
||||
this.logManager.logError(message, error),
|
||||
getLogManager().logError(message, error),
|
||||
}).then(() => {
|
||||
this.sillyTavernProcess = null;
|
||||
})
|
||||
|
|
@ -555,7 +550,7 @@ export class SillyTavernManager {
|
|||
try {
|
||||
await this.stopFrontend();
|
||||
} catch (error) {
|
||||
this.logManager.logError(
|
||||
getLogManager().logError(
|
||||
'Error during SillyTavernManager cleanup:',
|
||||
error as Error
|
||||
);
|
||||
|
|
@ -563,3 +558,12 @@ export class SillyTavernManager {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
let sillyTavernManagerInstance: SillyTavernManager;
|
||||
|
||||
export function getSillyTavernManager(): SillyTavernManager {
|
||||
if (!sillyTavernManagerInstance) {
|
||||
sillyTavernManagerInstance = new SillyTavernManager();
|
||||
}
|
||||
return sillyTavernManagerInstance;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,8 @@
|
|||
import {
|
||||
BrowserWindow,
|
||||
app,
|
||||
shell,
|
||||
nativeImage,
|
||||
screen,
|
||||
Menu,
|
||||
clipboard,
|
||||
} from 'electron';
|
||||
import { BrowserWindow, app, shell, screen, Menu, clipboard } from 'electron';
|
||||
import { join } from 'path';
|
||||
import { stripVTControlCharacters } from 'util';
|
||||
import { PRODUCT_NAME } from '../../constants';
|
||||
import type { IPCChannel, IPCChannelPayloads } from '@/types/ipc';
|
||||
import { getAssetPath } from '@/utils/path';
|
||||
|
||||
export class WindowManager {
|
||||
private mainWindow: BrowserWindow | null = null;
|
||||
|
|
@ -20,14 +11,7 @@ export class WindowManager {
|
|||
return process.env.NODE_ENV === 'development' || !app.isPackaged;
|
||||
}
|
||||
|
||||
private getIconPath(): string {
|
||||
return getAssetPath('icon.png');
|
||||
}
|
||||
|
||||
createMainWindow(): BrowserWindow {
|
||||
const iconPath = this.getIconPath();
|
||||
const iconImage = nativeImage.createFromPath(iconPath);
|
||||
|
||||
const { workAreaSize } = screen.getPrimaryDisplay();
|
||||
const windowHeight = Math.floor(workAreaSize.height * 0.86);
|
||||
|
||||
|
|
@ -35,7 +19,6 @@ export class WindowManager {
|
|||
width: 1000,
|
||||
height: windowHeight,
|
||||
frame: false,
|
||||
icon: iconImage,
|
||||
title: PRODUCT_NAME,
|
||||
show: false,
|
||||
backgroundColor: '#ffffff',
|
||||
|
|
@ -49,10 +32,6 @@ export class WindowManager {
|
|||
},
|
||||
});
|
||||
|
||||
if (process.platform === 'linux') {
|
||||
this.mainWindow.setIcon(iconImage);
|
||||
}
|
||||
|
||||
this.mainWindow.once('ready-to-show', () => {
|
||||
this.mainWindow?.show();
|
||||
});
|
||||
|
|
@ -223,3 +202,12 @@ export class WindowManager {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
let windowManagerInstance: WindowManager;
|
||||
|
||||
export function getWindowManager(): WindowManager {
|
||||
if (!windowManagerInstance) {
|
||||
windowManagerInstance = new WindowManager();
|
||||
}
|
||||
return windowManagerInstance;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue