new UI elements for MoE config items, Done button for settings modal,

This commit is contained in:
Egor 2025-08-31 01:44:38 -07:00
parent 35a5ae94ff
commit 0b829cbfe2
13 changed files with 195 additions and 49 deletions

View file

@ -193,11 +193,18 @@ jobs:
install -dm755 "${pkgdir}/usr/share/icons/hicolor/512x512/apps" install -dm755 "${pkgdir}/usr/share/icons/hicolor/512x512/apps"
install -dm755 "${pkgdir}/usr/share/pixmaps" install -dm755 "${pkgdir}/usr/share/pixmaps"
if [ -f "${pkgdir}/opt/gerbil/usr/share/icons/hicolor/513x513/apps/gerbil.png" ]; then if [ -f "${pkgdir}/opt/gerbil/usr/share/icons/hicolor/513x513/apps/Gerbil.png" ]; then
cp "${pkgdir}/opt/gerbil/usr/share/icons/hicolor/513x513/apps/gerbil.png" "${pkgdir}/usr/share/icons/hicolor/512x512/apps/gerbil.png" cp "${pkgdir}/opt/gerbil/usr/share/icons/hicolor/513x513/apps/Gerbil.png" "${pkgdir}/usr/share/icons/hicolor/512x512/apps/gerbil.png"
cp "${pkgdir}/opt/gerbil/usr/share/icons/hicolor/513x513/apps/gerbil.png" "${pkgdir}/usr/share/pixmaps/gerbil.png" cp "${pkgdir}/opt/gerbil/usr/share/icons/hicolor/513x513/apps/Gerbil.png" "${pkgdir}/usr/share/pixmaps/gerbil.png"
else else
echo "Warning: Could not find icon.png in expected locations" echo "Warning: Could not find Gerbil.png in expected locations"
find "${pkgdir}/opt/gerbil" -name "*erbil*.png" -type f | head -1 | while read icon_file; do
if [ -n "$icon_file" ]; then
echo "Found icon at: $icon_file"
cp "$icon_file" "${pkgdir}/usr/share/icons/hicolor/512x512/apps/gerbil.png"
cp "$icon_file" "${pkgdir}/usr/share/pixmaps/gerbil.png"
fi
done
fi fi
} }
EOF EOF

View file

@ -1,7 +1,7 @@
{ {
"name": "gerbil", "name": "gerbil",
"productName": "Gerbil", "productName": "Gerbil",
"version": "0.9.0", "version": "0.9.1",
"description": "Run Large Language Models locally", "description": "Run Large Language Models locally",
"main": "out/main/index.js", "main": "out/main/index.js",
"homepage": "./", "homepage": "./",

View file

@ -1,4 +1,4 @@
import { Stack, Group, Text, TextInput } from '@mantine/core'; import { Stack, Group, Text, TextInput, NumberInput } from '@mantine/core';
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { InfoTooltip } from '@/components/InfoTooltip'; import { InfoTooltip } from '@/components/InfoTooltip';
import { CheckboxWithTooltip } from '@/components/CheckboxWithTooltip'; import { CheckboxWithTooltip } from '@/components/CheckboxWithTooltip';
@ -16,6 +16,8 @@ export const AdvancedTab = () => {
quantmatmul, quantmatmul,
usemmap, usemmap,
backend, backend,
moecpu,
moeexperts,
handleAdditionalArgumentsChange, handleAdditionalArgumentsChange,
handleNoshiftChange, handleNoshiftChange,
handleFlashattentionChange, handleFlashattentionChange,
@ -24,6 +26,8 @@ export const AdvancedTab = () => {
handleLowvramChange, handleLowvramChange,
handleQuantmatmulChange, handleQuantmatmulChange,
handleUsemmapChange, handleUsemmapChange,
handleMoecpuChange,
handleMoeexpertsChange,
} = useLaunchConfig(); } = useLaunchConfig();
const [backendSupport, setBackendSupport] = useState<{ const [backendSupport, setBackendSupport] = useState<{
noavx2: boolean; noavx2: boolean;
@ -124,6 +128,55 @@ export const AdvancedTab = () => {
</Stack> </Stack>
</div> </div>
<div>
<Group gap="xs" align="center" mb="md">
<Text size="sm" fw={600}>
Mixture of Experts (MoE) Settings
</Text>
</Group>
<Stack gap="md">
<Group gap="lg" align="flex-start" wrap="nowrap">
<div style={{ flex: 1, minWidth: 200 }}>
<Group gap="xs" align="center" mb="xs">
<Text size="sm" fw={500}>
MoE Experts
</Text>
<InfoTooltip label="How many experts to use for MoE models. Set to -1 to follow GGUF metadata (default), or specify a specific number of experts." />
</Group>
<NumberInput
value={moeexperts}
onChange={(value) =>
handleMoeexpertsChange(Number(value) || -1)
}
min={-1}
max={128}
step={1}
size="sm"
placeholder="-1"
/>
</div>
<div style={{ flex: 1, minWidth: 200 }}>
<Group gap="xs" align="center" mb="xs">
<Text size="sm" fw={500}>
MoE CPU Layers
</Text>
<InfoTooltip label="Keep the Mixture of Experts (MoE) weights of the first N layers in the CPU. Set to 0 to disable (default), or specify the number of layers to keep on CPU." />
</Group>
<NumberInput
value={moecpu}
onChange={(value) => handleMoecpuChange(Number(value) || 0)}
min={0}
max={999}
step={1}
size="sm"
placeholder="0"
/>
</div>
</Group>
</Stack>
</div>
<div> <div>
<Group gap="xs" align="center" mb="md"> <Group gap="xs" align="center" mb="md">
<Text size="sm" fw={600}> <Text size="sm" fw={600}>

View file

@ -51,8 +51,8 @@ export const LaunchScreen = ({
usemmap, usemmap,
backend, backend,
gpuDeviceSelection, gpuDeviceSelection,
tensorSplit,
gpuPlatform, gpuPlatform,
tensorSplit,
sdmodel, sdmodel,
sdt5xxl, sdt5xxl,
sdclipl, sdclipl,
@ -61,6 +61,8 @@ export const LaunchScreen = ({
sdvae, sdvae,
sdlora, sdlora,
sdconvdirect, sdconvdirect,
moecpu,
moeexperts,
parseAndApplyConfigFile, parseAndApplyConfigFile,
loadConfigFromFile, loadConfigFromFile,
handleModelPathChange, handleModelPathChange,
@ -172,6 +174,8 @@ export const LaunchScreen = ({
noavx2, noavx2,
failsafe, failsafe,
usemmap, usemmap,
moecpu,
moeexperts,
usecuda: backend === 'cuda' || backend === 'rocm', usecuda: backend === 'cuda' || backend === 'rocm',
usevulkan: backend === 'vulkan', usevulkan: backend === 'vulkan',
useclblast: backend === 'clblast', useclblast: backend === 'clblast',
@ -280,6 +284,8 @@ export const LaunchScreen = ({
sdvae, sdvae,
sdlora, sdlora,
sdconvdirect, sdconvdirect,
moecpu,
moeexperts,
}); });
}; };
@ -303,7 +309,23 @@ export const LaunchScreen = ({
onLoadConfigFiles={loadConfigFiles} onLoadConfigFiles={loadConfigFiles}
/> />
<Tabs value={activeTab} onChange={setActiveTab}> <Tabs
value={activeTab}
onChange={setActiveTab}
styles={{
root: {
maxHeight: '22rem',
display: 'flex',
flexDirection: 'column',
},
panel: {
flex: 1,
overflow: 'auto',
paddingTop: '1rem',
paddingRight: '0.5rem',
},
}}
>
<Tabs.List> <Tabs.List>
<Tabs.Tab value="general">General</Tabs.Tab> <Tabs.Tab value="general">General</Tabs.Tab>
<Tabs.Tab value="image">Image Generation</Tabs.Tab> <Tabs.Tab value="image">Image Generation</Tabs.Tab>
@ -311,23 +333,21 @@ export const LaunchScreen = ({
<Tabs.Tab value="advanced">Advanced</Tabs.Tab> <Tabs.Tab value="advanced">Advanced</Tabs.Tab>
</Tabs.List> </Tabs.List>
<div> <Tabs.Panel value="general">
<Tabs.Panel value="general" pt="md">
<GeneralTab /> <GeneralTab />
</Tabs.Panel> </Tabs.Panel>
<Tabs.Panel value="advanced" pt="md"> <Tabs.Panel value="advanced">
<AdvancedTab /> <AdvancedTab />
</Tabs.Panel> </Tabs.Panel>
<Tabs.Panel value="network" pt="md"> <Tabs.Panel value="network">
<NetworkTab /> <NetworkTab />
</Tabs.Panel> </Tabs.Panel>
<Tabs.Panel value="image" pt="md"> <Tabs.Panel value="image">
<ImageGenerationTab /> <ImageGenerationTab />
</Tabs.Panel> </Tabs.Panel>
</div>
</Tabs> </Tabs>
<Group justify="flex-end" pt="md"> <Group justify="flex-end" pt="md">

View file

@ -1,5 +1,5 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { Modal, Tabs, Text, Group, rem } from '@mantine/core'; import { Modal, Tabs, Text, Group, rem, Button, Box } from '@mantine/core';
import { Settings, Palette, SlidersHorizontal, GitBranch } from 'lucide-react'; import { Settings, Palette, SlidersHorizontal, GitBranch } from 'lucide-react';
import { GeneralTab } from '@/components/settings/GeneralTab'; import { GeneralTab } from '@/components/settings/GeneralTab';
import { VersionsTab } from '@/components/settings/VersionsTab'; import { VersionsTab } from '@/components/settings/VersionsTab';
@ -61,11 +61,15 @@ export const SettingsModal = ({
lockScroll={false} lockScroll={false}
styles={{ styles={{
body: { body: {
height: '400px', height: '440px',
padding: 0, padding: 0,
display: 'flex',
flexDirection: 'column',
position: 'relative',
}, },
content: { content: {
height: '500px', height: '500px',
paddingBottom: 0,
}, },
}} }}
transitionProps={{ transitionProps={{
@ -79,7 +83,8 @@ export const SettingsModal = ({
variant="pills" variant="pills"
styles={{ styles={{
root: { root: {
height: '100%', flex: 1,
minHeight: 0,
}, },
panel: { panel: {
height: '100%', height: '100%',
@ -123,7 +128,7 @@ export const SettingsModal = ({
</Tabs.Panel> </Tabs.Panel>
{showVersionsTab && ( {showVersionsTab && (
<Tabs.Panel value="versions" style={{ overflow: 'visible' }}> <Tabs.Panel value="versions">
<VersionsTab /> <VersionsTab />
</Tabs.Panel> </Tabs.Panel>
)} )}
@ -132,6 +137,20 @@ export const SettingsModal = ({
<AppearanceTab /> <AppearanceTab />
</Tabs.Panel> </Tabs.Panel>
</Tabs> </Tabs>
<Box
style={{
backgroundColor: 'var(--mantine-color-body)',
padding: '1.5rem',
display: 'flex',
justifyContent: 'flex-end',
flexShrink: 0,
}}
>
<Button onClick={onClose} variant="filled">
Done
</Button>
</Box>
</Modal> </Modal>
); );
}; };

View file

@ -246,11 +246,10 @@ export const VersionsTab = () => {
return ( return (
<> <>
<Group justify="space-between" align="center" mb="lg"> <Group justify="space-between" align="center" mb="sm">
<div> <div>
<Text fw={500}>Available Versions</Text>
{latestRelease && ( {latestRelease && (
<Group gap="xs" mt={4}> <Group gap="xs">
<Text size="sm" c="dimmed"> <Text size="sm" c="dimmed">
Latest release: {latestRelease.release.tag_name} Latest release: {latestRelease.release.tag_name}
</Text> </Text>

View file

@ -39,6 +39,8 @@ export const useLaunchConfig = () => {
sdvae: state.sdvae, sdvae: state.sdvae,
sdlora: state.sdlora, sdlora: state.sdlora,
sdconvdirect: state.sdconvdirect, sdconvdirect: state.sdconvdirect,
moecpu: state.moecpu,
moeexperts: state.moeexperts,
handleGpuLayersChange: state.setGpuLayers, handleGpuLayersChange: state.setGpuLayers,
handleAutoGpuLayersChange: state.setAutoGpuLayers, handleAutoGpuLayersChange: state.setAutoGpuLayers,
@ -71,6 +73,8 @@ export const useLaunchConfig = () => {
handleSdvaeChange: state.setSdvae, handleSdvaeChange: state.setSdvae,
handleSdloraChange: state.setSdlora, handleSdloraChange: state.setSdlora,
handleSdconvdirectChange: state.setSdconvdirect, handleSdconvdirectChange: state.setSdconvdirect,
handleMoecpuChange: state.setMoecpu,
handleMoeexpertsChange: state.setMoeexperts,
parseAndApplyConfigFile: state.parseAndApplyConfigFile, parseAndApplyConfigFile: state.parseAndApplyConfigFile,
loadConfigFromFile: state.loadConfigFromFile, loadConfigFromFile: state.loadConfigFromFile,

View file

@ -38,6 +38,8 @@ interface LaunchArgs {
sdvae: string; sdvae: string;
sdlora: string; sdlora: string;
sdconvdirect: SdConvDirectMode; sdconvdirect: SdConvDirectMode;
moecpu: number;
moeexperts: number;
} }
const buildModelArgs = ( const buildModelArgs = (
@ -124,6 +126,14 @@ const buildConfigArgs = (
} }
}); });
if (launchArgs.moeexperts !== -1) {
args.push('--moeexperts', launchArgs.moeexperts.toString());
}
if (launchArgs.moecpu > 0) {
args.push('--moecpu', launchArgs.moecpu.toString());
}
return args; return args;
}; };

View file

@ -1,4 +1,4 @@
import { BrowserWindow, app, Menu, shell, nativeImage } from 'electron'; import { BrowserWindow, app, Menu, shell, nativeImage, screen } from 'electron';
import * as os from 'os'; import * as os from 'os';
import { join } from 'path'; import { join } from 'path';
import { stripVTControlCharacters } from 'util'; import { stripVTControlCharacters } from 'util';
@ -25,9 +25,12 @@ export class WindowManager {
const iconPath = this.getIconPath(); const iconPath = this.getIconPath();
const iconImage = nativeImage.createFromPath(iconPath); const iconImage = nativeImage.createFromPath(iconPath);
const { workAreaSize } = screen.getPrimaryDisplay();
const windowHeight = Math.floor(workAreaSize.height * 0.9);
this.mainWindow = new BrowserWindow({ this.mainWindow = new BrowserWindow({
width: 1000, width: 1000,
height: 600, height: windowHeight,
icon: iconImage, icon: iconImage,
title: PRODUCT_NAME, title: PRODUCT_NAME,
show: false, show: false,

View file

@ -8,34 +8,34 @@
font-family: font-family:
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
margin: 0; margin: 0;
padding: 20px; padding: 1.25rem;
background: #f5f5f5; background: #f5f5f5;
color: #333; color: #333;
line-height: 1.6; line-height: 1.6;
} }
.container { .container {
max-width: 400px; max-width: 25rem;
margin: 0 auto; margin: 0 auto;
background: white; background: white;
padding: 30px; padding: 1.875rem;
border-radius: 8px; border-radius: 0.5rem;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); box-shadow: 0 0.125rem 0.625rem rgba(0, 0, 0, 0.1);
text-align: center; text-align: center;
} }
h1 { h1 {
margin: 0 0 20px 0; margin: 0 0 1.25rem 0;
color: #2c3e50; color: #2c3e50;
font-size: 24px; font-size: 1.5rem;
} }
.version-info { .version-info {
text-align: left; text-align: left;
background: #f8f9fa; background: #f8f9fa;
padding: 15px; padding: 0.9375rem;
border-radius: 4px; border-radius: 0.25rem;
margin: 20px 0; margin: 1.25rem 0;
font-family: 'Monaco', 'Menlo', monospace; font-family: 'Monaco', 'Menlo', monospace;
font-size: 12px; font-size: 0.75rem;
line-height: 1.4; line-height: 1.4;
} }
.github-link { .github-link {
@ -47,18 +47,18 @@
text-decoration: underline; text-decoration: underline;
} }
.buttons { .buttons {
margin-top: 20px; margin-top: 1.25rem;
display: flex; display: flex;
gap: 10px; gap: 0.625rem;
justify-content: center; justify-content: center;
} }
button { button {
padding: 8px 16px; padding: 0.5rem 1rem;
border: 1px solid #ddd; border: 0.0625rem solid #ddd;
border-radius: 4px; border-radius: 0.25rem;
background: white; background: white;
cursor: pointer; cursor: pointer;
font-size: 14px; font-size: 0.875rem;
} }
button:hover { button:hover {
background: #f0f0f0; background: #f0f0f0;
@ -80,7 +80,7 @@
} }
.container { .container {
background: #2d2d2d !important; background: #2d2d2d !important;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3) !important; box-shadow: 0 0.125rem 0.625rem rgba(0, 0, 0, 0.3) !important;
} }
h1 { h1 {
color: #ffffff !important; color: #ffffff !important;

View file

@ -35,6 +35,8 @@ interface LaunchConfigState {
sdvae: string; sdvae: string;
sdlora: string; sdlora: string;
sdconvdirect: SdConvDirectMode; sdconvdirect: SdConvDirectMode;
moecpu: number;
moeexperts: number;
setGpuLayers: (layers: number) => void; setGpuLayers: (layers: number) => void;
setAutoGpuLayers: (auto: boolean) => void; setAutoGpuLayers: (auto: boolean) => void;
@ -67,6 +69,8 @@ interface LaunchConfigState {
setSdvae: (vae: string) => void; setSdvae: (vae: string) => void;
setSdlora: (loraModel: string) => void; setSdlora: (loraModel: string) => void;
setSdconvdirect: (mode: SdConvDirectMode) => void; setSdconvdirect: (mode: SdConvDirectMode) => void;
setMoecpu: (moecpu: number) => void;
setMoeexperts: (moeexperts: number) => void;
parseAndApplyConfigFile: (configPath: string) => Promise<void>; parseAndApplyConfigFile: (configPath: string) => Promise<void>;
loadConfigFromFile: ( loadConfigFromFile: (
@ -117,6 +121,8 @@ export const useLaunchConfigStore = create<LaunchConfigState>((set, get) => ({
sdvae: '', sdvae: '',
sdlora: '', sdlora: '',
sdconvdirect: 'off' as const, sdconvdirect: 'off' as const,
moecpu: 0,
moeexperts: -1,
setGpuLayers: (layers) => set({ gpuLayers: layers }), setGpuLayers: (layers) => set({ gpuLayers: layers }),
setAutoGpuLayers: (auto) => set({ autoGpuLayers: auto }), setAutoGpuLayers: (auto) => set({ autoGpuLayers: auto }),
@ -154,6 +160,8 @@ export const useLaunchConfigStore = create<LaunchConfigState>((set, get) => ({
setSdvae: (vae) => set({ sdvae: vae }), setSdvae: (vae) => set({ sdvae: vae }),
setSdlora: (loraModel) => set({ sdlora: loraModel }), setSdlora: (loraModel) => set({ sdlora: loraModel }),
setSdconvdirect: (mode) => set({ sdconvdirect: mode }), setSdconvdirect: (mode) => set({ sdconvdirect: mode }),
setMoecpu: (moeCpu) => set({ moecpu: moeCpu }),
setMoeexperts: (moeExperts) => set({ moeexperts: moeExperts }),
// eslint-disable-next-line sonarjs/cognitive-complexity // eslint-disable-next-line sonarjs/cognitive-complexity
parseAndApplyConfigFile: async (configPath: string) => { parseAndApplyConfigFile: async (configPath: string) => {
@ -340,6 +348,18 @@ export const useLaunchConfigStore = create<LaunchConfigState>((set, get) => ({
updates.sdconvdirect = configData.sdconvdirect as SdConvDirectMode; updates.sdconvdirect = configData.sdconvdirect as SdConvDirectMode;
} }
if (typeof configData.moecpu === 'number') {
updates.moecpu = configData.moecpu;
} else {
updates.moecpu = 0;
}
if (typeof configData.moeexperts === 'number') {
updates.moeexperts = configData.moeexperts;
} else {
updates.moeexperts = -1;
}
set(updates); set(updates);
} }
}, },

View file

@ -91,3 +91,12 @@ body {
transform: scale(1) rotate(0deg); transform: scale(1) rotate(0deg);
} }
} }
.mantine-Tooltip-tooltip {
background-color: var(--mantine-color-dark-6) !important;
color: var(--mantine-color-white) !important;
}
.mantine-Tooltip-arrow {
background-color: var(--mantine-color-dark-6) !important;
}

View file

@ -87,6 +87,8 @@ export interface KoboldConfig {
sdlora?: string; sdlora?: string;
sdconvdirect?: string; sdconvdirect?: string;
additionalArguments?: string; additionalArguments?: string;
moecpu?: number;
moeexperts?: number;
autoGpuLayers?: boolean; autoGpuLayers?: boolean;
model?: string; model?: string;
backend?: string; backend?: string;