(null);
const handleServerReady = useCallback(
(url: string) => {
@@ -28,6 +32,12 @@ export const InterfaceScreen = ({
[onTabChange]
);
+ useEffect(() => {
+ if (activeTab === 'terminal' && terminalTabRef.current) {
+ terminalTabRef.current.scrollToBottom();
+ }
+ }, [activeTab]);
+
return (
diff --git a/src/main/ipc.ts b/src/main/ipc.ts
index 49817b9..02f1d8d 100644
--- a/src/main/ipc.ts
+++ b/src/main/ipc.ts
@@ -8,6 +8,7 @@ import type { OpenWebUIManager } from '@/main/managers/OpenWebUIManager';
import type { WindowManager } from '@/main/managers/WindowManager';
import { HardwareManager } from '@/main/managers/HardwareManager';
import { BinaryManager } from '@/main/managers/BinaryManager';
+import type { FrontendPreference } from '@/types';
export class IPCHandlers {
private koboldManager: KoboldCppManager;
@@ -41,10 +42,14 @@ export class IPCHandlers {
private async launchKoboldCppWithCustomFrontends(args: string[] = []) {
try {
- const result = await this.koboldManager.launchKoboldCpp(args);
+ const frontendPreference = (await this.configManager.get(
+ 'frontendPreference'
+ )) as FrontendPreference;
- const frontendPreference =
- await this.configManager.get('frontendPreference');
+ const result = await this.koboldManager.launchKoboldCpp(
+ args,
+ frontendPreference
+ );
if (frontendPreference === 'sillytavern') {
this.sillyTavernManager.startFrontend(args);
diff --git a/src/main/managers/KoboldCppManager.ts b/src/main/managers/KoboldCppManager.ts
index 1548705..d44ce4d 100644
--- a/src/main/managers/KoboldCppManager.ts
+++ b/src/main/managers/KoboldCppManager.ts
@@ -11,6 +11,7 @@ import {
chmod,
readFile,
writeFile,
+ copyFile,
} from 'fs/promises';
import { dialog } from 'electron';
import axios from 'axios';
@@ -27,11 +28,14 @@ import {
} from '@/constants/patches';
import { pathExists, readJsonFile, writeJsonFile } from '@/utils/fs';
import { stripAssetExtensions } from '@/utils/version';
+import { parseKoboldConfig } from '@/utils/kobold';
+import { getAssetPath } from '@/utils/path';
import type {
GitHubAsset,
InstalledVersion,
KoboldConfig,
} from '@/types/electron';
+import type { FrontendPreference } from '@/types';
export class KoboldCppManager {
private koboldProcess: ChildProcess | null = null;
@@ -318,6 +322,29 @@ export class KoboldCppManager {
}
}
+ private async patchKcppSduiEmbd(unpackedDir: string): Promise {
+ try {
+ const possiblePaths = [
+ join(unpackedDir, '_internal', 'kcpp_sdui.embd'),
+ join(unpackedDir, 'kcpp_sdui.embd'),
+ ];
+
+ const sourceAssetPath = getAssetPath('kcpp_sdui.embd');
+
+ for (const targetPath of possiblePaths) {
+ if (await pathExists(targetPath)) {
+ await copyFile(sourceAssetPath, targetPath);
+ break;
+ }
+ }
+ } catch (error) {
+ this.logManager.logError(
+ 'Failed to patch kcpp_sdui.embd:',
+ error as Error
+ );
+ }
+ }
+
private async getLauncherPath(unpackedDir: string): Promise {
const extensions =
process.platform === 'win32' ? ['.exe', ''] : ['', '.exe'];
@@ -618,7 +645,8 @@ export class KoboldCppManager {
}
async launchKoboldCpp(
- args: string[] = []
+ args: string[] = [],
+ frontendPreference: FrontendPreference = 'koboldcpp'
): Promise<{ success: boolean; pid?: number; error?: string }> {
try {
if (this.koboldProcess) {
@@ -646,7 +674,18 @@ export class KoboldCppManager {
.split(/[/\\]/)
.slice(0, -1)
.join('/');
- await this.patchKliteEmbd(binaryDir);
+
+ const { isImageMode } = parseKoboldConfig(args);
+
+ if (frontendPreference === 'koboldcpp') {
+ if (isImageMode) {
+ await this.patchKcppSduiEmbd(binaryDir);
+ } else {
+ await this.patchKliteEmbd(binaryDir);
+ }
+ } else if (frontendPreference === 'openwebui' && isImageMode) {
+ await this.patchKcppSduiEmbd(binaryDir);
+ }
const finalArgs = [...args];
diff --git a/src/main/managers/OpenWebUIManager.ts b/src/main/managers/OpenWebUIManager.ts
index fc4f777..05daf8e 100644
--- a/src/main/managers/OpenWebUIManager.ts
+++ b/src/main/managers/OpenWebUIManager.ts
@@ -162,9 +162,6 @@ export class OpenWebUIManager {
} = parseKoboldConfig(args);
if (isImageMode) {
- this.windowManager.sendKoboldOutput(
- 'Open WebUI does not support image generation mode. Please use KoboldAI Lite or SillyTavern for image generation.'
- );
return;
}
diff --git a/src/main/managers/WindowManager.ts b/src/main/managers/WindowManager.ts
index 18237d4..28c4584 100644
--- a/src/main/managers/WindowManager.ts
+++ b/src/main/managers/WindowManager.ts
@@ -11,6 +11,7 @@ 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,11 +21,7 @@ export class WindowManager {
}
private getIconPath(): string {
- if (process.env.NODE_ENV === 'development') {
- const projectRoot = join(__dirname, '../..');
- return join(projectRoot, 'src/assets/icon.png');
- }
- return join(process.resourcesPath, 'assets/icon.png');
+ return getAssetPath('icon.png');
}
createMainWindow(): BrowserWindow {
diff --git a/src/main/utils/assets.ts b/src/main/utils/assets.ts
new file mode 100644
index 0000000..bbf2683
--- /dev/null
+++ b/src/main/utils/assets.ts
@@ -0,0 +1,9 @@
+import { join } from 'path';
+import { app } from 'electron';
+
+export const getAssetPath = (filename: string): string => {
+ if (process.env.NODE_ENV === 'development' || !app.isPackaged) {
+ return join(__dirname, '../..', 'src/assets', filename);
+ }
+ return join(process.resourcesPath, 'assets', filename);
+};
diff --git a/src/utils/path.ts b/src/utils/path.ts
index d996264..ed5de66 100644
--- a/src/utils/path.ts
+++ b/src/utils/path.ts
@@ -1,7 +1,15 @@
import { join } from 'path';
import { homedir } from 'os';
+import { app } from 'electron';
import { PRODUCT_NAME, CONFIG_FILE_NAME } from '@/constants';
+export function getAssetPath(filename: string): string {
+ if (process.env.NODE_ENV === 'development' || !app.isPackaged) {
+ return join(__dirname, '../..', 'src/assets', filename);
+ }
+ return join(process.resourcesPath, 'assets', filename);
+}
+
export function getConfigDir(): string {
return join(getConfigDirPath(), CONFIG_FILE_NAME);
}