mirror of
https://github.com/lone-cloud/gerbil
synced 2026-06-03 09:33:10 -07:00
always scroll to bottom when switching to the terminal tab, overwrite existing sdui.embd with a cleaner version
This commit is contained in:
parent
985cc53fa6
commit
db572b408e
11 changed files with 780 additions and 173 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "gerbil",
|
"name": "gerbil",
|
||||||
"productName": "Gerbil",
|
"productName": "Gerbil",
|
||||||
"version": "1.0.5",
|
"version": "1.0.6",
|
||||||
"description": "Run Large Language Models locally",
|
"description": "Run Large Language Models locally",
|
||||||
"main": "out/main/index.js",
|
"main": "out/main/index.js",
|
||||||
"homepage": "./",
|
"homepage": "./",
|
||||||
|
|
|
||||||
524
src/assets/kcpp_sdui.embd
Normal file
524
src/assets/kcpp_sdui.embd
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -25,11 +25,11 @@ import type { FrontendPreference, InterfaceTab, Screen } from '@/types';
|
||||||
|
|
||||||
interface TitleBarProps {
|
interface TitleBarProps {
|
||||||
currentScreen: Screen;
|
currentScreen: Screen;
|
||||||
currentTab?: InterfaceTab;
|
currentTab: InterfaceTab;
|
||||||
onTabChange?: (tab: InterfaceTab) => void;
|
onTabChange: (tab: InterfaceTab) => void;
|
||||||
onEject?: () => void;
|
onEject: () => void;
|
||||||
onOpenSettings?: () => void;
|
onOpenSettings: () => void;
|
||||||
frontendPreference?: FrontendPreference;
|
frontendPreference: FrontendPreference;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TitleBar = ({
|
export const TitleBar = ({
|
||||||
|
|
@ -150,9 +150,9 @@ export const TitleBar = ({
|
||||||
value={currentTab}
|
value={currentTab}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
if (value === 'eject') {
|
if (value === 'eject') {
|
||||||
onEject?.();
|
onEject();
|
||||||
} else {
|
} else {
|
||||||
onTabChange?.(value as InterfaceTab);
|
onTabChange(value as InterfaceTab);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onDropdownOpen={() => setIsSelectOpen(true)}
|
onDropdownOpen={() => setIsSelectOpen(true)}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,10 @@
|
||||||
import { useState, useEffect, useRef } from 'react';
|
import {
|
||||||
|
useState,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
forwardRef,
|
||||||
|
useImperativeHandle,
|
||||||
|
} from 'react';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
ScrollArea,
|
ScrollArea,
|
||||||
|
|
@ -17,163 +23,174 @@ interface TerminalTabProps {
|
||||||
frontendPreference?: FrontendPreference;
|
frontendPreference?: FrontendPreference;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TerminalTab = ({
|
export interface TerminalTabRef {
|
||||||
onServerReady,
|
scrollToBottom: () => void;
|
||||||
frontendPreference = 'koboldcpp',
|
}
|
||||||
}: TerminalTabProps) => {
|
|
||||||
const { host, port, isImageGenerationMode } = useLaunchConfigStore();
|
|
||||||
const computedColorScheme = useComputedColorScheme('light', {
|
|
||||||
getInitialValueInEffect: false,
|
|
||||||
});
|
|
||||||
const [terminalContent, setTerminalContent] = useState<string>('');
|
|
||||||
const [isUserScrolling, setIsUserScrolling] = useState<boolean>(false);
|
|
||||||
const [shouldAutoScroll, setShouldAutoScroll] = useState<boolean>(true);
|
|
||||||
const scrollAreaRef = useRef<HTMLDivElement>(null);
|
|
||||||
const viewportRef = useRef<HTMLDivElement>(null);
|
|
||||||
const lastScrollTop = useRef<number>(0);
|
|
||||||
|
|
||||||
const isDark = computedColorScheme === 'dark';
|
export const TerminalTab = forwardRef<TerminalTabRef, TerminalTabProps>(
|
||||||
|
({ onServerReady, frontendPreference = 'koboldcpp' }, ref) => {
|
||||||
const handleScroll = ({ y }: { y: number }) => {
|
const { host, port, isImageGenerationMode } = useLaunchConfigStore();
|
||||||
if (!viewportRef.current) return;
|
const computedColorScheme = useComputedColorScheme('light', {
|
||||||
|
getInitialValueInEffect: false,
|
||||||
const { scrollHeight, clientHeight } = viewportRef.current;
|
|
||||||
const isAtBottomNow = y + clientHeight >= scrollHeight - 10;
|
|
||||||
|
|
||||||
if (y < lastScrollTop.current) {
|
|
||||||
setIsUserScrolling(true);
|
|
||||||
setShouldAutoScroll(false);
|
|
||||||
} else if (isAtBottomNow) {
|
|
||||||
setIsUserScrolling(false);
|
|
||||||
setShouldAutoScroll(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
lastScrollTop.current = y;
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (shouldAutoScroll && !isUserScrolling && viewportRef.current) {
|
|
||||||
const viewport = viewportRef.current;
|
|
||||||
viewport.scrollTop = viewport.scrollHeight;
|
|
||||||
}
|
|
||||||
}, [terminalContent, shouldAutoScroll, isUserScrolling]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const cleanup = window.electronAPI.kobold.onKoboldOutput((data: string) => {
|
|
||||||
setTerminalContent((prev) => {
|
|
||||||
const newData = data.toString();
|
|
||||||
|
|
||||||
if (onServerReady) {
|
|
||||||
const serverHost = host || 'localhost';
|
|
||||||
const serverPort = port || 5001;
|
|
||||||
|
|
||||||
let signalToCheck: string = SERVER_READY_SIGNALS.KOBOLDCPP;
|
|
||||||
|
|
||||||
if (frontendPreference === 'sillytavern') {
|
|
||||||
signalToCheck = SERVER_READY_SIGNALS.SILLYTAVERN;
|
|
||||||
} else if (
|
|
||||||
frontendPreference === 'openwebui' &&
|
|
||||||
!isImageGenerationMode
|
|
||||||
) {
|
|
||||||
signalToCheck = SERVER_READY_SIGNALS.OPENWEBUI;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newData.includes(signalToCheck)) {
|
|
||||||
setTimeout(
|
|
||||||
() => onServerReady(`http://${serverHost}:${serverPort}`),
|
|
||||||
1500
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return handleTerminalOutput(prev, newData);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
const [terminalContent, setTerminalContent] = useState<string>('');
|
||||||
|
const [isUserScrolling, setIsUserScrolling] = useState<boolean>(false);
|
||||||
|
const [shouldAutoScroll, setShouldAutoScroll] = useState<boolean>(true);
|
||||||
|
const scrollAreaRef = useRef<HTMLDivElement>(null);
|
||||||
|
const viewportRef = useRef<HTMLDivElement>(null);
|
||||||
|
const lastScrollTop = useRef<number>(0);
|
||||||
|
|
||||||
return cleanup;
|
const isDark = computedColorScheme === 'dark';
|
||||||
}, [onServerReady, host, port, frontendPreference, isImageGenerationMode]);
|
|
||||||
|
|
||||||
const scrollToBottom = () => {
|
const handleScroll = ({ y }: { y: number }) => {
|
||||||
if (viewportRef.current) {
|
if (!viewportRef.current) return;
|
||||||
const viewport = viewportRef.current;
|
|
||||||
viewport.scrollTop = viewport.scrollHeight;
|
|
||||||
setShouldAutoScroll(true);
|
|
||||||
setIsUserScrolling(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
const { scrollHeight, clientHeight } = viewportRef.current;
|
||||||
<Box
|
const isAtBottomNow = y + clientHeight >= scrollHeight - 10;
|
||||||
style={{
|
|
||||||
height: `calc(100vh - ${TITLEBAR_HEIGHT})`,
|
if (y < lastScrollTop.current) {
|
||||||
display: 'flex',
|
setIsUserScrolling(true);
|
||||||
flexDirection: 'column',
|
setShouldAutoScroll(false);
|
||||||
backgroundColor: isDark
|
} else if (isAtBottomNow) {
|
||||||
? 'var(--mantine-color-dark-filled)'
|
setIsUserScrolling(false);
|
||||||
: 'var(--mantine-color-gray-0)',
|
setShouldAutoScroll(true);
|
||||||
borderRadius: 'inherit',
|
}
|
||||||
position: 'relative',
|
|
||||||
}}
|
lastScrollTop.current = y;
|
||||||
>
|
};
|
||||||
<ScrollArea
|
|
||||||
ref={scrollAreaRef}
|
useEffect(() => {
|
||||||
viewportRef={viewportRef}
|
if (shouldAutoScroll && !isUserScrolling && viewportRef.current) {
|
||||||
onScrollPositionChange={handleScroll}
|
const viewport = viewportRef.current;
|
||||||
|
viewport.scrollTop = viewport.scrollHeight;
|
||||||
|
}
|
||||||
|
}, [terminalContent, shouldAutoScroll, isUserScrolling]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const cleanup = window.electronAPI.kobold.onKoboldOutput(
|
||||||
|
(data: string) => {
|
||||||
|
setTerminalContent((prev) => {
|
||||||
|
const newData = data.toString();
|
||||||
|
|
||||||
|
if (onServerReady) {
|
||||||
|
const serverHost = host || 'localhost';
|
||||||
|
const serverPort = port || 5001;
|
||||||
|
|
||||||
|
let signalToCheck: string = SERVER_READY_SIGNALS.KOBOLDCPP;
|
||||||
|
|
||||||
|
if (frontendPreference === 'sillytavern') {
|
||||||
|
signalToCheck = SERVER_READY_SIGNALS.SILLYTAVERN;
|
||||||
|
} else if (
|
||||||
|
frontendPreference === 'openwebui' &&
|
||||||
|
!isImageGenerationMode
|
||||||
|
) {
|
||||||
|
signalToCheck = SERVER_READY_SIGNALS.OPENWEBUI;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newData.includes(signalToCheck)) {
|
||||||
|
setTimeout(
|
||||||
|
() => onServerReady(`http://${serverHost}:${serverPort}`),
|
||||||
|
1500
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return handleTerminalOutput(prev, newData);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return cleanup;
|
||||||
|
}, [onServerReady, host, port, frontendPreference, isImageGenerationMode]);
|
||||||
|
|
||||||
|
const scrollToBottom = () => {
|
||||||
|
if (viewportRef.current) {
|
||||||
|
const viewport = viewportRef.current;
|
||||||
|
viewport.scrollTop = viewport.scrollHeight;
|
||||||
|
setShouldAutoScroll(true);
|
||||||
|
setIsUserScrolling(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
scrollToBottom,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
height: `calc(100vh - ${TITLEBAR_HEIGHT})`,
|
||||||
fontFamily: 'Monaco, Menlo, Ubuntu Mono, monospace',
|
display: 'flex',
|
||||||
fontSize: '0.875em',
|
flexDirection: 'column',
|
||||||
lineHeight: 1.4,
|
backgroundColor: isDark
|
||||||
|
? 'var(--mantine-color-dark-filled)'
|
||||||
|
: 'var(--mantine-color-gray-0)',
|
||||||
|
borderRadius: 'inherit',
|
||||||
|
position: 'relative',
|
||||||
}}
|
}}
|
||||||
scrollbarSize={8}
|
|
||||||
offsetScrollbars={false}
|
|
||||||
>
|
>
|
||||||
<Box p="md">
|
<ScrollArea
|
||||||
{terminalContent.length === 0 ? (
|
ref={scrollAreaRef}
|
||||||
<Text c="dimmed" style={{ fontFamily: 'inherit' }}>
|
viewportRef={viewportRef}
|
||||||
Starting...
|
onScrollPositionChange={handleScroll}
|
||||||
</Text>
|
|
||||||
) : (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
margin: 0,
|
|
||||||
fontFamily:
|
|
||||||
'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
|
|
||||||
fontSize: '0.875em',
|
|
||||||
lineHeight: 1.4,
|
|
||||||
color: isDark
|
|
||||||
? 'var(--mantine-color-gray-0)'
|
|
||||||
: 'var(--mantine-color-dark-filled)',
|
|
||||||
whiteSpace: 'pre-wrap',
|
|
||||||
wordBreak: 'break-word',
|
|
||||||
}}
|
|
||||||
dangerouslySetInnerHTML={{
|
|
||||||
__html: processTerminalContent(terminalContent),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</ScrollArea>
|
|
||||||
|
|
||||||
{isUserScrolling && !shouldAutoScroll && (
|
|
||||||
<ActionIcon
|
|
||||||
variant="filled"
|
|
||||||
color="blue"
|
|
||||||
size="lg"
|
|
||||||
radius="xl"
|
|
||||||
onClick={scrollToBottom}
|
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
flex: 1,
|
||||||
bottom: '1.25rem',
|
fontFamily: 'Monaco, Menlo, Ubuntu Mono, monospace',
|
||||||
right: '1.25rem',
|
fontSize: '0.875em',
|
||||||
zIndex: 10,
|
lineHeight: 1.4,
|
||||||
boxShadow: '0 0.125rem 0.5rem rgba(0, 0, 0, 0.3)',
|
|
||||||
}}
|
}}
|
||||||
aria-label="Scroll to bottom"
|
scrollbarSize={8}
|
||||||
|
offsetScrollbars={false}
|
||||||
>
|
>
|
||||||
<ChevronDown size={20} />
|
<Box p="md">
|
||||||
</ActionIcon>
|
{terminalContent.length === 0 ? (
|
||||||
)}
|
<Text c="dimmed" style={{ fontFamily: 'inherit' }}>
|
||||||
</Box>
|
Starting...
|
||||||
);
|
</Text>
|
||||||
};
|
) : (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
margin: 0,
|
||||||
|
fontFamily:
|
||||||
|
'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
|
||||||
|
fontSize: '0.875em',
|
||||||
|
lineHeight: 1.4,
|
||||||
|
color: isDark
|
||||||
|
? 'var(--mantine-color-gray-0)'
|
||||||
|
: 'var(--mantine-color-dark-filled)',
|
||||||
|
whiteSpace: 'pre-wrap',
|
||||||
|
wordBreak: 'break-word',
|
||||||
|
}}
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: processTerminalContent(terminalContent),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</ScrollArea>
|
||||||
|
|
||||||
|
{isUserScrolling && !shouldAutoScroll && (
|
||||||
|
<ActionIcon
|
||||||
|
variant="filled"
|
||||||
|
color="blue"
|
||||||
|
size="lg"
|
||||||
|
radius="xl"
|
||||||
|
onClick={scrollToBottom}
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: '1.25rem',
|
||||||
|
right: '1.25rem',
|
||||||
|
zIndex: 10,
|
||||||
|
boxShadow: '0 0.125rem 0.5rem rgba(0, 0, 0, 0.3)',
|
||||||
|
}}
|
||||||
|
aria-label="Scroll to bottom"
|
||||||
|
>
|
||||||
|
<ChevronDown size={20} />
|
||||||
|
</ActionIcon>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
TerminalTab.displayName = 'TerminalTab';
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
import { useState, useCallback } from 'react';
|
import { useState, useCallback, useRef, useEffect } from 'react';
|
||||||
import { ServerTab } from '@/components/screens/Interface/ServerTab';
|
import { ServerTab } from '@/components/screens/Interface/ServerTab';
|
||||||
import { TerminalTab } from '@/components/screens/Interface/TerminalTab';
|
import {
|
||||||
|
TerminalTab,
|
||||||
|
type TerminalTabRef,
|
||||||
|
} from '@/components/screens/Interface/TerminalTab';
|
||||||
import type { InterfaceTab, FrontendPreference } from '@/types';
|
import type { InterfaceTab, FrontendPreference } from '@/types';
|
||||||
|
|
||||||
interface InterfaceScreenProps {
|
interface InterfaceScreenProps {
|
||||||
|
|
@ -16,6 +19,7 @@ export const InterfaceScreen = ({
|
||||||
}: InterfaceScreenProps) => {
|
}: InterfaceScreenProps) => {
|
||||||
const [serverUrl, setServerUrl] = useState<string>('');
|
const [serverUrl, setServerUrl] = useState<string>('');
|
||||||
const [isServerReady, setIsServerReady] = useState<boolean>(false);
|
const [isServerReady, setIsServerReady] = useState<boolean>(false);
|
||||||
|
const terminalTabRef = useRef<TerminalTabRef>(null);
|
||||||
|
|
||||||
const handleServerReady = useCallback(
|
const handleServerReady = useCallback(
|
||||||
(url: string) => {
|
(url: string) => {
|
||||||
|
|
@ -28,6 +32,12 @@ export const InterfaceScreen = ({
|
||||||
[onTabChange]
|
[onTabChange]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (activeTab === 'terminal' && terminalTabRef.current) {
|
||||||
|
terminalTabRef.current.scrollToBottom();
|
||||||
|
}
|
||||||
|
}, [activeTab]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
|
|
@ -56,6 +66,7 @@ export const InterfaceScreen = ({
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TerminalTab
|
<TerminalTab
|
||||||
|
ref={terminalTabRef}
|
||||||
onServerReady={handleServerReady}
|
onServerReady={handleServerReady}
|
||||||
frontendPreference={frontendPreference}
|
frontendPreference={frontendPreference}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import type { OpenWebUIManager } from '@/main/managers/OpenWebUIManager';
|
||||||
import type { WindowManager } from '@/main/managers/WindowManager';
|
import type { WindowManager } from '@/main/managers/WindowManager';
|
||||||
import { HardwareManager } from '@/main/managers/HardwareManager';
|
import { HardwareManager } from '@/main/managers/HardwareManager';
|
||||||
import { BinaryManager } from '@/main/managers/BinaryManager';
|
import { BinaryManager } from '@/main/managers/BinaryManager';
|
||||||
|
import type { FrontendPreference } from '@/types';
|
||||||
|
|
||||||
export class IPCHandlers {
|
export class IPCHandlers {
|
||||||
private koboldManager: KoboldCppManager;
|
private koboldManager: KoboldCppManager;
|
||||||
|
|
@ -41,10 +42,14 @@ export class IPCHandlers {
|
||||||
|
|
||||||
private async launchKoboldCppWithCustomFrontends(args: string[] = []) {
|
private async launchKoboldCppWithCustomFrontends(args: string[] = []) {
|
||||||
try {
|
try {
|
||||||
const result = await this.koboldManager.launchKoboldCpp(args);
|
const frontendPreference = (await this.configManager.get(
|
||||||
|
'frontendPreference'
|
||||||
|
)) as FrontendPreference;
|
||||||
|
|
||||||
const frontendPreference =
|
const result = await this.koboldManager.launchKoboldCpp(
|
||||||
await this.configManager.get('frontendPreference');
|
args,
|
||||||
|
frontendPreference
|
||||||
|
);
|
||||||
|
|
||||||
if (frontendPreference === 'sillytavern') {
|
if (frontendPreference === 'sillytavern') {
|
||||||
this.sillyTavernManager.startFrontend(args);
|
this.sillyTavernManager.startFrontend(args);
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import {
|
||||||
chmod,
|
chmod,
|
||||||
readFile,
|
readFile,
|
||||||
writeFile,
|
writeFile,
|
||||||
|
copyFile,
|
||||||
} from 'fs/promises';
|
} from 'fs/promises';
|
||||||
import { dialog } from 'electron';
|
import { dialog } from 'electron';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
@ -27,11 +28,14 @@ import {
|
||||||
} from '@/constants/patches';
|
} from '@/constants/patches';
|
||||||
import { pathExists, readJsonFile, writeJsonFile } from '@/utils/fs';
|
import { pathExists, readJsonFile, writeJsonFile } from '@/utils/fs';
|
||||||
import { stripAssetExtensions } from '@/utils/version';
|
import { stripAssetExtensions } from '@/utils/version';
|
||||||
|
import { parseKoboldConfig } from '@/utils/kobold';
|
||||||
|
import { getAssetPath } from '@/utils/path';
|
||||||
import type {
|
import type {
|
||||||
GitHubAsset,
|
GitHubAsset,
|
||||||
InstalledVersion,
|
InstalledVersion,
|
||||||
KoboldConfig,
|
KoboldConfig,
|
||||||
} from '@/types/electron';
|
} from '@/types/electron';
|
||||||
|
import type { FrontendPreference } from '@/types';
|
||||||
|
|
||||||
export class KoboldCppManager {
|
export class KoboldCppManager {
|
||||||
private koboldProcess: ChildProcess | null = null;
|
private koboldProcess: ChildProcess | null = null;
|
||||||
|
|
@ -318,6 +322,29 @@ export class KoboldCppManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async patchKcppSduiEmbd(unpackedDir: string): Promise<void> {
|
||||||
|
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<string | null> {
|
private async getLauncherPath(unpackedDir: string): Promise<string | null> {
|
||||||
const extensions =
|
const extensions =
|
||||||
process.platform === 'win32' ? ['.exe', ''] : ['', '.exe'];
|
process.platform === 'win32' ? ['.exe', ''] : ['', '.exe'];
|
||||||
|
|
@ -618,7 +645,8 @@ export class KoboldCppManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
async launchKoboldCpp(
|
async launchKoboldCpp(
|
||||||
args: string[] = []
|
args: string[] = [],
|
||||||
|
frontendPreference: FrontendPreference = 'koboldcpp'
|
||||||
): Promise<{ success: boolean; pid?: number; error?: string }> {
|
): Promise<{ success: boolean; pid?: number; error?: string }> {
|
||||||
try {
|
try {
|
||||||
if (this.koboldProcess) {
|
if (this.koboldProcess) {
|
||||||
|
|
@ -646,7 +674,18 @@ export class KoboldCppManager {
|
||||||
.split(/[/\\]/)
|
.split(/[/\\]/)
|
||||||
.slice(0, -1)
|
.slice(0, -1)
|
||||||
.join('/');
|
.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];
|
const finalArgs = [...args];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -162,9 +162,6 @@ export class OpenWebUIManager {
|
||||||
} = parseKoboldConfig(args);
|
} = parseKoboldConfig(args);
|
||||||
|
|
||||||
if (isImageMode) {
|
if (isImageMode) {
|
||||||
this.windowManager.sendKoboldOutput(
|
|
||||||
'Open WebUI does not support image generation mode. Please use KoboldAI Lite or SillyTavern for image generation.'
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import { join } from 'path';
|
||||||
import { stripVTControlCharacters } from 'util';
|
import { stripVTControlCharacters } from 'util';
|
||||||
import { PRODUCT_NAME } from '../../constants';
|
import { PRODUCT_NAME } from '../../constants';
|
||||||
import type { IPCChannel, IPCChannelPayloads } from '@/types/ipc';
|
import type { IPCChannel, IPCChannelPayloads } from '@/types/ipc';
|
||||||
|
import { getAssetPath } from '@/utils/path';
|
||||||
|
|
||||||
export class WindowManager {
|
export class WindowManager {
|
||||||
private mainWindow: BrowserWindow | null = null;
|
private mainWindow: BrowserWindow | null = null;
|
||||||
|
|
@ -20,11 +21,7 @@ export class WindowManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private getIconPath(): string {
|
private getIconPath(): string {
|
||||||
if (process.env.NODE_ENV === 'development') {
|
return getAssetPath('icon.png');
|
||||||
const projectRoot = join(__dirname, '../..');
|
|
||||||
return join(projectRoot, 'src/assets/icon.png');
|
|
||||||
}
|
|
||||||
return join(process.resourcesPath, 'assets/icon.png');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
createMainWindow(): BrowserWindow {
|
createMainWindow(): BrowserWindow {
|
||||||
|
|
|
||||||
9
src/main/utils/assets.ts
Normal file
9
src/main/utils/assets.ts
Normal file
|
|
@ -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);
|
||||||
|
};
|
||||||
|
|
@ -1,7 +1,15 @@
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { homedir } from 'os';
|
import { homedir } from 'os';
|
||||||
|
import { app } from 'electron';
|
||||||
import { PRODUCT_NAME, CONFIG_FILE_NAME } from '@/constants';
|
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 {
|
export function getConfigDir(): string {
|
||||||
return join(getConfigDirPath(), CONFIG_FILE_NAME);
|
return join(getConfigDirPath(), CONFIG_FILE_NAME);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue