mirror of
https://github.com/lone-cloud/gerbil
synced 2026-06-04 12:13:28 -07:00
better titlebar support for the AppHeader, new additional argument option modal
This commit is contained in:
parent
88c901a657
commit
4ddacd5ae6
11 changed files with 913 additions and 197 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "gerbil",
|
"name": "gerbil",
|
||||||
"productName": "Gerbil",
|
"productName": "Gerbil",
|
||||||
"version": "0.9.3",
|
"version": "0.9.4",
|
||||||
"description": "Run Large Language Models locally",
|
"description": "Run Large Language Models locally",
|
||||||
"main": "out/main/index.js",
|
"main": "out/main/index.js",
|
||||||
"homepage": "./",
|
"homepage": "./",
|
||||||
|
|
|
||||||
44
src/App.tsx
44
src/App.tsx
|
|
@ -11,6 +11,7 @@ import { ScreenTransition } from '@/components/ScreenTransition';
|
||||||
import { TitleBar } from '@/components/TitleBar';
|
import { TitleBar } from '@/components/TitleBar';
|
||||||
import { useUpdateChecker } from '@/hooks/useUpdateChecker';
|
import { useUpdateChecker } from '@/hooks/useUpdateChecker';
|
||||||
import { useKoboldVersions } from '@/hooks/useKoboldVersions';
|
import { useKoboldVersions } from '@/hooks/useKoboldVersions';
|
||||||
|
import { useModalStore } from '@/stores/modal';
|
||||||
import { safeExecute } from '@/utils/logger';
|
import { safeExecute } from '@/utils/logger';
|
||||||
import { TITLEBAR_HEIGHT } from '@/constants';
|
import { TITLEBAR_HEIGHT } from '@/constants';
|
||||||
import type { DownloadItem } from '@/types/electron';
|
import type { DownloadItem } from '@/types/electron';
|
||||||
|
|
@ -18,15 +19,15 @@ import type { InterfaceTab, FrontendPreference, Screen } from '@/types';
|
||||||
|
|
||||||
export const App = () => {
|
export const App = () => {
|
||||||
const [currentScreen, setCurrentScreen] = useState<Screen | null>(null);
|
const [currentScreen, setCurrentScreen] = useState<Screen | null>(null);
|
||||||
const [settingsOpened, setSettingsOpened] = useState(false);
|
|
||||||
const [hasInitialized, setHasInitialized] = useState(false);
|
const [hasInitialized, setHasInitialized] = useState(false);
|
||||||
const [showEjectModal, setShowEjectModal] = useState(false);
|
|
||||||
const [activeInterfaceTab, setActiveInterfaceTab] =
|
const [activeInterfaceTab, setActiveInterfaceTab] =
|
||||||
useState<InterfaceTab>('terminal');
|
useState<InterfaceTab>('terminal');
|
||||||
const [isImageGenerationMode, setIsImageGenerationMode] = useState(false);
|
const [isImageGenerationMode, setIsImageGenerationMode] = useState(false);
|
||||||
const [frontendPreference, setFrontendPreference] =
|
const [frontendPreference, setFrontendPreference] =
|
||||||
useState<FrontendPreference>('koboldcpp');
|
useState<FrontendPreference>('koboldcpp');
|
||||||
|
|
||||||
|
const { modals, setModalOpen } = useModalStore();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
updateInfo: binaryUpdateInfo,
|
updateInfo: binaryUpdateInfo,
|
||||||
showUpdateModal,
|
showUpdateModal,
|
||||||
|
|
@ -120,7 +121,7 @@ export const App = () => {
|
||||||
if (skipEjectConfirmation) {
|
if (skipEjectConfirmation) {
|
||||||
performEject();
|
performEject();
|
||||||
} else {
|
} else {
|
||||||
setShowEjectModal(true);
|
setModalOpen('ejectConfirm', true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -147,31 +148,20 @@ export const App = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const isAnyModalOpen = settingsOpened || showEjectModal || showUpdateModal;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppShell
|
<AppShell
|
||||||
header={{ height: TITLEBAR_HEIGHT }}
|
header={{ height: TITLEBAR_HEIGHT }}
|
||||||
padding={currentScreen === 'interface' ? 0 : 'md'}
|
padding={currentScreen === 'interface' ? 0 : 'md'}
|
||||||
>
|
>
|
||||||
<AppShell.Header style={{ display: 'flex', flexDirection: 'column' }}>
|
<TitleBar
|
||||||
<TitleBar
|
currentScreen={currentScreen || 'welcome'}
|
||||||
currentScreen={currentScreen || 'welcome'}
|
currentTab={activeInterfaceTab}
|
||||||
currentTab={activeInterfaceTab}
|
onTabChange={setActiveInterfaceTab}
|
||||||
onTabChange={setActiveInterfaceTab}
|
onEject={handleEject}
|
||||||
onEject={handleEject}
|
onOpenSettings={() => setModalOpen('settings', true)}
|
||||||
onOpenSettings={() => setSettingsOpened(true)}
|
/>
|
||||||
isModalOpen={isAnyModalOpen}
|
|
||||||
/>
|
<AppShell.Main>
|
||||||
</AppShell.Header>
|
|
||||||
<AppShell.Main
|
|
||||||
style={{
|
|
||||||
position: 'relative',
|
|
||||||
overflow: 'hidden',
|
|
||||||
minHeight:
|
|
||||||
currentScreen === 'welcome' ? '100vh' : 'calc(100vh - 2rem)',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{currentScreen === null ? (
|
{currentScreen === null ? (
|
||||||
<Center h="100%" style={{ minHeight: '25rem' }}>
|
<Center h="100%" style={{ minHeight: '25rem' }}>
|
||||||
<Stack align="center" gap="lg">
|
<Stack align="center" gap="lg">
|
||||||
|
|
@ -238,9 +228,9 @@ export const App = () => {
|
||||||
)}
|
)}
|
||||||
</AppShell.Main>
|
</AppShell.Main>
|
||||||
<SettingsModal
|
<SettingsModal
|
||||||
opened={settingsOpened}
|
opened={modals.settings}
|
||||||
onClose={async () => {
|
onClose={async () => {
|
||||||
setSettingsOpened(false);
|
setModalOpen('settings', false);
|
||||||
const preference = await safeExecute(
|
const preference = await safeExecute(
|
||||||
() =>
|
() =>
|
||||||
window.electronAPI.config.get(
|
window.electronAPI.config.get(
|
||||||
|
|
@ -253,8 +243,8 @@ export const App = () => {
|
||||||
currentScreen={currentScreen || undefined}
|
currentScreen={currentScreen || undefined}
|
||||||
/>
|
/>
|
||||||
<EjectConfirmModal
|
<EjectConfirmModal
|
||||||
opened={showEjectModal}
|
opened={modals.ejectConfirm}
|
||||||
onClose={() => setShowEjectModal(false)}
|
onClose={() => setModalOpen('ejectConfirm', false)}
|
||||||
onConfirm={handleEjectConfirm}
|
onConfirm={handleEjectConfirm}
|
||||||
/>
|
/>
|
||||||
</AppShell>
|
</AppShell>
|
||||||
|
|
|
||||||
643
src/components/CommandLineArgumentsModal.tsx
Normal file
643
src/components/CommandLineArgumentsModal.tsx
Normal file
|
|
@ -0,0 +1,643 @@
|
||||||
|
import {
|
||||||
|
Modal,
|
||||||
|
Text,
|
||||||
|
Stack,
|
||||||
|
Group,
|
||||||
|
Badge,
|
||||||
|
Accordion,
|
||||||
|
Code,
|
||||||
|
TextInput,
|
||||||
|
Button,
|
||||||
|
} from '@mantine/core';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
interface CommandLineArgumentsModalProps {
|
||||||
|
opened: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onAddArgument?: (argument: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ArgumentInfo {
|
||||||
|
flag: string;
|
||||||
|
aliases?: string[];
|
||||||
|
description: string;
|
||||||
|
metavar?: string;
|
||||||
|
default?: string | number;
|
||||||
|
type?: string;
|
||||||
|
choices?: string[];
|
||||||
|
category: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const UI_COVERED_ARGS = new Set([
|
||||||
|
'--model',
|
||||||
|
'--sdmodel',
|
||||||
|
'--gpulayers',
|
||||||
|
'--contextsize',
|
||||||
|
'--port',
|
||||||
|
'--host',
|
||||||
|
'--multiuser',
|
||||||
|
'--multiplayer',
|
||||||
|
'--remotetunnel',
|
||||||
|
'--nocertify',
|
||||||
|
'--websearch',
|
||||||
|
'--noshift',
|
||||||
|
'--flashattention',
|
||||||
|
'--noavx2',
|
||||||
|
'--failsafe',
|
||||||
|
'--usemmap',
|
||||||
|
'--moeexperts',
|
||||||
|
'--moecpu',
|
||||||
|
'--usecuda',
|
||||||
|
'--usevulkan',
|
||||||
|
'--useclblast',
|
||||||
|
'--tensorsplit',
|
||||||
|
'--sdt5xxl',
|
||||||
|
'--sdclipl',
|
||||||
|
'--sdclipg',
|
||||||
|
'--sdphotomaker',
|
||||||
|
'--sdvae',
|
||||||
|
'--sdlora',
|
||||||
|
'--sdflashattention',
|
||||||
|
'--sdconvdirect',
|
||||||
|
]);
|
||||||
|
|
||||||
|
const COMMAND_LINE_ARGUMENTS: ArgumentInfo[] = [
|
||||||
|
{
|
||||||
|
flag: '--threads',
|
||||||
|
aliases: ['-t'],
|
||||||
|
description:
|
||||||
|
'Use a custom number of threads if specified. Otherwise, uses an amount based on CPU cores',
|
||||||
|
metavar: '[threads]',
|
||||||
|
type: 'int',
|
||||||
|
category: 'Basic',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--launch',
|
||||||
|
description: 'Launches a web browser when load is completed.',
|
||||||
|
type: 'boolean',
|
||||||
|
category: 'Basic',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--config',
|
||||||
|
description:
|
||||||
|
'Load settings from a .kcpps file. Other arguments will be ignored',
|
||||||
|
metavar: '[filename]',
|
||||||
|
type: 'string',
|
||||||
|
category: 'Basic',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--version',
|
||||||
|
description: 'Prints version and exits.',
|
||||||
|
type: 'boolean',
|
||||||
|
category: 'Advanced',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--analyze',
|
||||||
|
description:
|
||||||
|
'Reads the metadata, weight types and tensor names in any GGUF file.',
|
||||||
|
metavar: '[filename]',
|
||||||
|
default: '',
|
||||||
|
category: 'Advanced',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--maingpu',
|
||||||
|
aliases: ['--main-gpu', '-mg'],
|
||||||
|
description:
|
||||||
|
'Only used in a multi-gpu setup. Sets the index of the main GPU that will be used.',
|
||||||
|
metavar: '[Device ID]',
|
||||||
|
type: 'int',
|
||||||
|
default: -1,
|
||||||
|
category: 'Advanced',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--blasbatchsize',
|
||||||
|
aliases: ['--batch-size', '-b'],
|
||||||
|
description:
|
||||||
|
'Sets the batch size used in BLAS processing (default 512). Setting it to -1 disables BLAS mode, but keeps other benefits like GPU offload.',
|
||||||
|
type: 'int',
|
||||||
|
choices: [
|
||||||
|
'-1',
|
||||||
|
'16',
|
||||||
|
'32',
|
||||||
|
'64',
|
||||||
|
'128',
|
||||||
|
'256',
|
||||||
|
'512',
|
||||||
|
'1024',
|
||||||
|
'2048',
|
||||||
|
'4096',
|
||||||
|
],
|
||||||
|
default: 512,
|
||||||
|
category: 'Advanced',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--blasthreads',
|
||||||
|
aliases: ['--threads-batch'],
|
||||||
|
description:
|
||||||
|
'Use a different number of threads during BLAS if specified. Otherwise, has the same value as --threads',
|
||||||
|
metavar: '[threads]',
|
||||||
|
type: 'int',
|
||||||
|
default: 0,
|
||||||
|
category: 'Advanced',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--lora',
|
||||||
|
description: 'GGUF models only, applies a lora file on top of model.',
|
||||||
|
metavar: '[lora_filename]',
|
||||||
|
type: 'string[]',
|
||||||
|
category: 'Advanced',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--loramult',
|
||||||
|
description: 'Multiplier for the Text LORA model to be applied.',
|
||||||
|
metavar: '[amount]',
|
||||||
|
type: 'float',
|
||||||
|
default: 1.0,
|
||||||
|
category: 'Advanced',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--nofastforward',
|
||||||
|
description:
|
||||||
|
'If set, do not attempt to fast forward GGUF context (always reprocess). Will also enable noshift',
|
||||||
|
type: 'boolean',
|
||||||
|
category: 'Advanced',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--useswa',
|
||||||
|
description:
|
||||||
|
'If set, allows Sliding Window Attention (SWA) KV Cache, which saves memory but cannot be used with context shifting.',
|
||||||
|
type: 'boolean',
|
||||||
|
category: 'Advanced',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--ropeconfig',
|
||||||
|
description:
|
||||||
|
'If set, uses customized RoPE scaling from configured frequency scale and frequency base (e.g. --ropeconfig 0.25 10000). Otherwise, uses NTK-Aware scaling set automatically based on context size.',
|
||||||
|
metavar: '[rope-freq-scale] [rope-freq-base]',
|
||||||
|
default: '0.0 10000.0',
|
||||||
|
type: 'float[]',
|
||||||
|
category: 'Advanced',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--overridenativecontext',
|
||||||
|
description:
|
||||||
|
'Overrides the native trained context of the loaded model with a custom value to be used for Rope scaling.',
|
||||||
|
metavar: '[trained context]',
|
||||||
|
type: 'int',
|
||||||
|
default: 0,
|
||||||
|
category: 'Advanced',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--usemlock',
|
||||||
|
aliases: ['--mlock'],
|
||||||
|
description:
|
||||||
|
'Enables mlock, preventing the RAM used to load the model from being paged out. Not usually recommended.',
|
||||||
|
type: 'boolean',
|
||||||
|
category: 'Advanced',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--debugmode',
|
||||||
|
description: 'Shows additional debug info in the terminal.',
|
||||||
|
type: 'int',
|
||||||
|
default: 0,
|
||||||
|
category: 'Advanced',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--onready',
|
||||||
|
description:
|
||||||
|
'An optional shell command to execute after the model has been loaded.',
|
||||||
|
metavar: '[shell command]',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
category: 'Advanced',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--benchmark',
|
||||||
|
description:
|
||||||
|
'Do not start server, instead run benchmarks. If filename is provided, appends results to provided file.',
|
||||||
|
metavar: '[filename]',
|
||||||
|
type: 'string',
|
||||||
|
default: 'stdout',
|
||||||
|
category: 'Advanced',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--prompt',
|
||||||
|
aliases: ['-p'],
|
||||||
|
description:
|
||||||
|
'Passing a prompt string triggers a direct inference, loading the model, outputs the response to stdout and exits. Can be used alone or with benchmark.',
|
||||||
|
metavar: '[prompt]',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
category: 'Advanced',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--cli',
|
||||||
|
description:
|
||||||
|
'Does not launch KoboldCpp HTTP server. Instead, enables KoboldCpp from the command line, accepting interactive console input and displaying responses to the terminal.',
|
||||||
|
type: 'boolean',
|
||||||
|
category: 'Advanced',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--genlimit',
|
||||||
|
aliases: ['--promptlimit'],
|
||||||
|
description:
|
||||||
|
'Sets the maximum number of generated tokens, it will restrict all generations to this or lower. Also usable with --prompt or --benchmark.',
|
||||||
|
metavar: '[token limit]',
|
||||||
|
type: 'int',
|
||||||
|
default: 0,
|
||||||
|
category: 'Advanced',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--highpriority',
|
||||||
|
description:
|
||||||
|
'Experimental flag. If set, increases the process CPU priority, potentially speeding up generation. Use caution.',
|
||||||
|
type: 'boolean',
|
||||||
|
category: 'Advanced',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--foreground',
|
||||||
|
description:
|
||||||
|
'Windows only. Sends the terminal to the foreground every time a new prompt is generated. This helps avoid some idle slowdown issues.',
|
||||||
|
type: 'boolean',
|
||||||
|
category: 'Advanced',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--preloadstory',
|
||||||
|
description:
|
||||||
|
'Configures a prepared story json save file to be hosted on the server, which frontends (such as KoboldAI Lite) can access over the API.',
|
||||||
|
metavar: '[savefile]',
|
||||||
|
default: '',
|
||||||
|
category: 'Advanced',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--savedatafile',
|
||||||
|
description:
|
||||||
|
'If enabled, creates or opens a persistent database file on the server, that allows users to save and load their data remotely. A new file is created if it does not exist.',
|
||||||
|
metavar: '[savefile]',
|
||||||
|
default: '',
|
||||||
|
category: 'Advanced',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--quiet',
|
||||||
|
description:
|
||||||
|
'Enable quiet mode, which hides generation inputs and outputs in the terminal. Quiet mode is automatically enabled when running a horde worker.',
|
||||||
|
type: 'boolean',
|
||||||
|
category: 'Advanced',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--ssl',
|
||||||
|
description:
|
||||||
|
'Allows all content to be served over SSL instead. A valid UNENCRYPTED SSL cert and key .pem files must be provided',
|
||||||
|
metavar: '[cert_pem] [key_pem]',
|
||||||
|
type: 'string[]',
|
||||||
|
category: 'Advanced',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--password',
|
||||||
|
description:
|
||||||
|
'Enter a password required to use this instance. This key will be required for all text endpoints. Image endpoints are not secured.',
|
||||||
|
metavar: '[API key]',
|
||||||
|
default: 'None',
|
||||||
|
category: 'Advanced',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--mmproj',
|
||||||
|
description:
|
||||||
|
'Select a multimodal projector file for vision models like LLaVA.',
|
||||||
|
metavar: '[filename]',
|
||||||
|
default: '',
|
||||||
|
category: 'Multimodal',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--mmprojcpu',
|
||||||
|
aliases: ['--no-mmproj-offload'],
|
||||||
|
description: 'Force CLIP for Vision mmproj always on CPU.',
|
||||||
|
type: 'boolean',
|
||||||
|
category: 'Multimodal',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--visionmaxres',
|
||||||
|
description:
|
||||||
|
'Clamp MMProj vision maximum allowed resolution. Allowed values are between 512 to 2048 px (default 1024).',
|
||||||
|
metavar: '[max px]',
|
||||||
|
type: 'int',
|
||||||
|
default: 1024,
|
||||||
|
category: 'Multimodal',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--draftmodel',
|
||||||
|
aliases: ['--model-draft', '-md'],
|
||||||
|
description:
|
||||||
|
'Load a small draft model for speculative decoding. It will be fully offloaded. Vocab must match the main model.',
|
||||||
|
metavar: '[filename]',
|
||||||
|
default: '',
|
||||||
|
category: 'Speculative Decoding',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--draftamount',
|
||||||
|
aliases: ['--draft-max', '--draft-n'],
|
||||||
|
description: 'How many tokens to draft per chunk before verifying results',
|
||||||
|
metavar: '[tokens]',
|
||||||
|
type: 'int',
|
||||||
|
default: 16,
|
||||||
|
category: 'Speculative Decoding',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--draftgpulayers',
|
||||||
|
aliases: ['--gpu-layers-draft', '--n-gpu-layers-draft', '-ngld'],
|
||||||
|
description:
|
||||||
|
'How many layers to offload to GPU for the draft model (default=full offload)',
|
||||||
|
metavar: '[layers]',
|
||||||
|
type: 'int',
|
||||||
|
default: 999,
|
||||||
|
category: 'Speculative Decoding',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--quantkv',
|
||||||
|
description:
|
||||||
|
'Sets the KV cache data type quantization, 0=f16, 1=q8, 2=q4. Requires Flash Attention for full effect, otherwise only K cache is quantized.',
|
||||||
|
metavar: '[quantization level 0/1/2]',
|
||||||
|
type: 'int',
|
||||||
|
choices: ['0', '1', '2'],
|
||||||
|
default: 0,
|
||||||
|
category: 'Performance',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--defaultgenamt',
|
||||||
|
description:
|
||||||
|
'How many tokens to generate by default, if not specified. Must be smaller than context size. Usually, your frontend GUI will override this.',
|
||||||
|
type: 'int',
|
||||||
|
default: 768,
|
||||||
|
category: 'Performance',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--nobostoken',
|
||||||
|
description:
|
||||||
|
'Prevents BOS token from being added at the start of any prompt. Usually NOT recommended for most models.',
|
||||||
|
type: 'boolean',
|
||||||
|
category: 'Performance',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--enableguidance',
|
||||||
|
description:
|
||||||
|
'Enables the use of Classifier-Free-Guidance, which allows the use of negative prompts. Has performance and memory impact.',
|
||||||
|
type: 'boolean',
|
||||||
|
category: 'Performance',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--admin',
|
||||||
|
description:
|
||||||
|
'Enables admin mode, allowing you to unload and reload different configurations or models.',
|
||||||
|
type: 'boolean',
|
||||||
|
category: 'Administration',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--adminpassword',
|
||||||
|
description:
|
||||||
|
'Require a password to access admin functions. You are strongly advised to use one for publically accessible instances!',
|
||||||
|
metavar: '[password]',
|
||||||
|
default: 'None',
|
||||||
|
category: 'Administration',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--admindir',
|
||||||
|
description:
|
||||||
|
'Specify a directory to look for .kcpps configs in, which can be used to swap models.',
|
||||||
|
metavar: '[directory]',
|
||||||
|
default: '',
|
||||||
|
category: 'Administration',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--hordemodelname',
|
||||||
|
description: 'Sets your AI Horde display model name.',
|
||||||
|
metavar: '[name]',
|
||||||
|
default: '',
|
||||||
|
category: 'Horde Worker',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--hordeworkername',
|
||||||
|
description: 'Sets your AI Horde worker name.',
|
||||||
|
metavar: '[name]',
|
||||||
|
default: '',
|
||||||
|
category: 'Horde Worker',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--hordekey',
|
||||||
|
description: 'Sets your AI Horde API key.',
|
||||||
|
metavar: '[apikey]',
|
||||||
|
default: '',
|
||||||
|
category: 'Horde Worker',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--sdthreads',
|
||||||
|
description:
|
||||||
|
'Use a different number of threads for image generation if specified. Otherwise, has the same value as --threads.',
|
||||||
|
metavar: '[threads]',
|
||||||
|
type: 'int',
|
||||||
|
default: 0,
|
||||||
|
category: 'Image Generation',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--sdquant',
|
||||||
|
description:
|
||||||
|
'If specified, loads the model quantized to save memory. 0=off, 1=q8, 2=q4',
|
||||||
|
metavar: '[quantization level 0/1/2]',
|
||||||
|
type: 'int',
|
||||||
|
choices: ['0', '1', '2'],
|
||||||
|
default: 0,
|
||||||
|
category: 'Image Generation',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--whispermodel',
|
||||||
|
description:
|
||||||
|
'Specify a Whisper .bin model to enable Speech-To-Text transcription.',
|
||||||
|
metavar: '[filename]',
|
||||||
|
default: '',
|
||||||
|
category: 'Audio',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--ttsmodel',
|
||||||
|
description: 'Specify the TTS Text-To-Speech GGUF model.',
|
||||||
|
metavar: '[filename]',
|
||||||
|
default: '',
|
||||||
|
category: 'Audio',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--ttsgpu',
|
||||||
|
description: 'Use the GPU for TTS.',
|
||||||
|
type: 'boolean',
|
||||||
|
category: 'Audio',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--embeddingsmodel',
|
||||||
|
description:
|
||||||
|
'Specify an embeddings model to be loaded for generating embedding vectors.',
|
||||||
|
metavar: '[filename]',
|
||||||
|
default: '',
|
||||||
|
category: 'Embeddings',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flag: '--embeddingsgpu',
|
||||||
|
description:
|
||||||
|
'Attempts to offload layers of the embeddings model to GPU. Usually not needed.',
|
||||||
|
type: 'boolean',
|
||||||
|
category: 'Embeddings',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const AVAILABLE_ARGUMENTS = COMMAND_LINE_ARGUMENTS.filter(
|
||||||
|
(arg) => !UI_COVERED_ARGS.has(arg.flag)
|
||||||
|
);
|
||||||
|
|
||||||
|
export const CommandLineArgumentsModal = ({
|
||||||
|
opened,
|
||||||
|
onClose,
|
||||||
|
onAddArgument,
|
||||||
|
}: CommandLineArgumentsModalProps) => {
|
||||||
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
|
|
||||||
|
const argumentsByCategory = AVAILABLE_ARGUMENTS.reduce(
|
||||||
|
(acc, arg) => {
|
||||||
|
if (!acc[arg.category]) {
|
||||||
|
acc[arg.category] = [];
|
||||||
|
}
|
||||||
|
acc[arg.category].push(arg);
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as Record<string, ArgumentInfo[]>
|
||||||
|
);
|
||||||
|
|
||||||
|
const filteredCategories = Object.entries(argumentsByCategory).reduce(
|
||||||
|
(acc, [category, args]) => {
|
||||||
|
const filteredArgs = args.filter(
|
||||||
|
(arg) =>
|
||||||
|
arg.flag.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
|
arg.description.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
|
(arg.aliases &&
|
||||||
|
arg.aliases.some((alias) =>
|
||||||
|
alias.toLowerCase().includes(searchQuery.toLowerCase())
|
||||||
|
))
|
||||||
|
);
|
||||||
|
|
||||||
|
if (filteredArgs.length > 0) {
|
||||||
|
acc[category] = filteredArgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as Record<string, ArgumentInfo[]>
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleAddArgument = (arg: ArgumentInfo) => {
|
||||||
|
if (onAddArgument) {
|
||||||
|
let argumentToAdd = arg.flag;
|
||||||
|
|
||||||
|
if (arg.type !== 'boolean' && arg.metavar) {
|
||||||
|
argumentToAdd += ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
onAddArgument(argumentToAdd);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderArgument = (arg: ArgumentInfo) => (
|
||||||
|
<Stack
|
||||||
|
key={arg.flag}
|
||||||
|
gap="xs"
|
||||||
|
p="sm"
|
||||||
|
style={{ borderLeft: '3px solid var(--mantine-color-blue-4)' }}
|
||||||
|
>
|
||||||
|
<Group gap="xs" wrap="wrap" justify="space-between">
|
||||||
|
<Group gap="xs" wrap="wrap" style={{ flex: 1 }}>
|
||||||
|
<Code style={{ fontSize: '14px', fontWeight: 600 }}>{arg.flag}</Code>
|
||||||
|
{arg.aliases &&
|
||||||
|
arg.aliases.map((alias) => (
|
||||||
|
<Code key={alias} style={{ fontSize: '12px' }} c="dimmed">
|
||||||
|
{alias}
|
||||||
|
</Code>
|
||||||
|
))}
|
||||||
|
{arg.type && (
|
||||||
|
<Badge size="xs" variant="light" color="blue">
|
||||||
|
{arg.type}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
{onAddArgument && (
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
variant="light"
|
||||||
|
onClick={() => handleAddArgument(arg)}
|
||||||
|
>
|
||||||
|
Add
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Text size="sm" c="dimmed">
|
||||||
|
{arg.description}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
{(arg.metavar || arg.default !== undefined || arg.choices) && (
|
||||||
|
<Group gap="lg">
|
||||||
|
{arg.metavar && (
|
||||||
|
<Text size="xs" c="dimmed">
|
||||||
|
<strong>Format:</strong> {arg.metavar}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
{arg.default !== undefined && (
|
||||||
|
<Text size="xs" c="dimmed">
|
||||||
|
<strong>Default:</strong> {String(arg.default)}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
{arg.choices && (
|
||||||
|
<Text size="xs" c="dimmed">
|
||||||
|
<strong>Choices:</strong> {arg.choices.join(', ')}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
opened={opened}
|
||||||
|
onClose={onClose}
|
||||||
|
title="Available Command Line Arguments"
|
||||||
|
size="xl"
|
||||||
|
centered
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
<Text size="sm" c="dimmed">
|
||||||
|
These are additional command line arguments that can be added to the
|
||||||
|
"Additional Arguments" field.
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
placeholder="Search arguments..."
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(event) => setSearchQuery(event.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Accordion variant="separated">
|
||||||
|
{Object.entries(filteredCategories).map(([category, args]) => (
|
||||||
|
<Accordion.Item key={category} value={category}>
|
||||||
|
<Accordion.Control>
|
||||||
|
<Group justify="space-between">
|
||||||
|
<Text fw={600}>{category}</Text>
|
||||||
|
<Badge size="sm" variant="light">
|
||||||
|
{args.length} argument{args.length !== 1 ? 's' : ''}
|
||||||
|
</Badge>
|
||||||
|
</Group>
|
||||||
|
</Accordion.Control>
|
||||||
|
<Accordion.Panel>
|
||||||
|
<Stack gap="md">{args.map(renderArgument)}</Stack>
|
||||||
|
</Accordion.Panel>
|
||||||
|
</Accordion.Item>
|
||||||
|
))}
|
||||||
|
</Accordion>
|
||||||
|
</Stack>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -5,6 +5,7 @@ import {
|
||||||
Image,
|
Image,
|
||||||
Select,
|
Select,
|
||||||
useComputedColorScheme,
|
useComputedColorScheme,
|
||||||
|
AppShell,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import {
|
import {
|
||||||
Minus,
|
Minus,
|
||||||
|
|
@ -17,9 +18,10 @@ import {
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { soundAssets, playSound, initializeAudio } from '@/utils/sounds';
|
import { soundAssets, playSound, initializeAudio } from '@/utils/sounds';
|
||||||
import { useAppUpdateChecker } from '@/hooks/useAppUpdateChecker';
|
import { useAppUpdateChecker } from '@/hooks/useAppUpdateChecker';
|
||||||
|
import { useModalStore } from '@/stores/modal';
|
||||||
import iconUrl from '/icon.png';
|
import iconUrl from '/icon.png';
|
||||||
import type { InterfaceTab, Screen } from '@/types';
|
|
||||||
import { PRODUCT_NAME, TITLEBAR_HEIGHT } from '@/constants';
|
import { PRODUCT_NAME, TITLEBAR_HEIGHT } from '@/constants';
|
||||||
|
import type { InterfaceTab, Screen } from '@/types';
|
||||||
|
|
||||||
interface TitleBarProps {
|
interface TitleBarProps {
|
||||||
currentScreen: Screen;
|
currentScreen: Screen;
|
||||||
|
|
@ -27,7 +29,6 @@ interface TitleBarProps {
|
||||||
onTabChange?: (tab: InterfaceTab) => void;
|
onTabChange?: (tab: InterfaceTab) => void;
|
||||||
onEject?: () => void;
|
onEject?: () => void;
|
||||||
onOpenSettings?: () => void;
|
onOpenSettings?: () => void;
|
||||||
isModalOpen?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TitleBar = ({
|
export const TitleBar = ({
|
||||||
|
|
@ -36,12 +37,12 @@ export const TitleBar = ({
|
||||||
onTabChange,
|
onTabChange,
|
||||||
onEject,
|
onEject,
|
||||||
onOpenSettings,
|
onOpenSettings,
|
||||||
isModalOpen = false,
|
|
||||||
}: TitleBarProps) => {
|
}: TitleBarProps) => {
|
||||||
const computedColorScheme = useComputedColorScheme('light', {
|
const computedColorScheme = useComputedColorScheme('light', {
|
||||||
getInitialValueInEffect: false,
|
getInitialValueInEffect: false,
|
||||||
});
|
});
|
||||||
const { hasUpdate, openReleasePage } = useAppUpdateChecker();
|
const { hasUpdate, openReleasePage } = useAppUpdateChecker();
|
||||||
|
const { isAnyModalOpen } = useModalStore();
|
||||||
const [logoClickCount, setLogoClickCount] = useState(0);
|
const [logoClickCount, setLogoClickCount] = useState(0);
|
||||||
const [isElephantMode, setIsElephantMode] = useState(false);
|
const [isElephantMode, setIsElephantMode] = useState(false);
|
||||||
const [isMouseSqueaking, setIsMouseSqueaking] = useState(false);
|
const [isMouseSqueaking, setIsMouseSqueaking] = useState(false);
|
||||||
|
|
@ -87,171 +88,177 @@ export const TitleBar = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<AppShell.Header style={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
style={{
|
|
||||||
height: TITLEBAR_HEIGHT,
|
|
||||||
padding: '0.125rem 0.5rem',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
backgroundColor:
|
|
||||||
computedColorScheme === 'dark'
|
|
||||||
? 'var(--mantine-color-dark-7)'
|
|
||||||
: 'var(--mantine-color-gray-0)',
|
|
||||||
borderBottom: '1px solid var(--mantine-color-default-border)',
|
|
||||||
WebkitAppRegion: isModalOpen ? 'no-drag' : 'drag',
|
|
||||||
userSelect: 'none',
|
|
||||||
position: 'relative',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Group gap="0.5rem" align="center" style={{ WebkitAppRegion: 'no-drag' }}>
|
|
||||||
<Image
|
|
||||||
src={iconUrl}
|
|
||||||
alt={PRODUCT_NAME}
|
|
||||||
w={24}
|
|
||||||
h={24}
|
|
||||||
style={{
|
|
||||||
minWidth: 24,
|
|
||||||
minHeight: 24,
|
|
||||||
cursor: 'pointer',
|
|
||||||
userSelect: 'none',
|
|
||||||
transition: 'transform 0.15s ease-in-out',
|
|
||||||
transform: isElephantMode
|
|
||||||
? 'scale(1.3) rotate(5deg)'
|
|
||||||
: 'scale(1) rotate(0deg)',
|
|
||||||
animation: isElephantMode
|
|
||||||
? 'elephantShake 1.5s ease-in-out'
|
|
||||||
: isMouseSqueaking
|
|
||||||
? 'mouseSqueak 0.3s ease-in-out'
|
|
||||||
: 'none',
|
|
||||||
}}
|
|
||||||
onClick={handleLogoClick}
|
|
||||||
/>
|
|
||||||
</Group>
|
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
height: TITLEBAR_HEIGHT,
|
||||||
left: '50%',
|
padding: '0.125rem 0.5rem',
|
||||||
transform: 'translateX(-50%)',
|
display: 'flex',
|
||||||
WebkitAppRegion: 'no-drag',
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
backgroundColor:
|
||||||
|
computedColorScheme === 'dark'
|
||||||
|
? 'var(--mantine-color-dark-7)'
|
||||||
|
: 'var(--mantine-color-gray-0)',
|
||||||
|
borderBottom: '1px solid var(--mantine-color-default-border)',
|
||||||
|
WebkitAppRegion: isAnyModalOpen() ? 'no-drag' : 'drag',
|
||||||
|
userSelect: 'none',
|
||||||
|
position: 'relative',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{currentScreen === 'interface' && (
|
<Group
|
||||||
<Select
|
gap="0.5rem"
|
||||||
placeholder="Interface"
|
align="center"
|
||||||
value={currentTab}
|
style={{ WebkitAppRegion: 'no-drag' }}
|
||||||
onChange={(value) => {
|
|
||||||
if (value === 'eject') {
|
|
||||||
onEject?.();
|
|
||||||
} else {
|
|
||||||
onTabChange?.(value as InterfaceTab);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
data={[
|
|
||||||
{
|
|
||||||
value: 'chat',
|
|
||||||
label: 'Chat',
|
|
||||||
},
|
|
||||||
{ value: 'terminal', label: 'Terminal' },
|
|
||||||
{ value: 'eject', label: 'Eject' },
|
|
||||||
]}
|
|
||||||
allowDeselect={false}
|
|
||||||
variant="unstyled"
|
|
||||||
size="sm"
|
|
||||||
style={{
|
|
||||||
textAlign: 'center',
|
|
||||||
minWidth: '120px',
|
|
||||||
}}
|
|
||||||
styles={{
|
|
||||||
input: {
|
|
||||||
textAlign: 'center',
|
|
||||||
backgroundColor: 'transparent',
|
|
||||||
border: 'none',
|
|
||||||
fontWeight: 500,
|
|
||||||
userSelect: 'none',
|
|
||||||
cursor: 'pointer',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Group gap="0.125rem" style={{ WebkitAppRegion: 'no-drag' }}>
|
|
||||||
{hasUpdate && (
|
|
||||||
<ActionIcon
|
|
||||||
variant="subtle"
|
|
||||||
color="orange"
|
|
||||||
size="2rem"
|
|
||||||
onClick={openReleasePage}
|
|
||||||
aria-label="New release available"
|
|
||||||
style={{
|
|
||||||
borderRadius: '0.25rem',
|
|
||||||
margin: '0.125rem',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CircleFadingArrowUp size="1.25rem" />
|
|
||||||
</ActionIcon>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<ActionIcon
|
|
||||||
variant="subtle"
|
|
||||||
size="2rem"
|
|
||||||
onClick={onOpenSettings}
|
|
||||||
aria-label="Open settings"
|
|
||||||
style={{
|
|
||||||
borderRadius: '0.25rem',
|
|
||||||
margin: '0.125rem',
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Settings size="1.25rem" />
|
<Image
|
||||||
</ActionIcon>
|
src={iconUrl}
|
||||||
|
alt={PRODUCT_NAME}
|
||||||
|
w={24}
|
||||||
|
h={24}
|
||||||
|
style={{
|
||||||
|
minWidth: 24,
|
||||||
|
minHeight: 24,
|
||||||
|
cursor: 'pointer',
|
||||||
|
userSelect: 'none',
|
||||||
|
transition: 'transform 0.15s ease-in-out',
|
||||||
|
transform: isElephantMode
|
||||||
|
? 'scale(1.3) rotate(5deg)'
|
||||||
|
: 'scale(1) rotate(0deg)',
|
||||||
|
animation: isElephantMode
|
||||||
|
? 'elephantShake 1.5s ease-in-out'
|
||||||
|
: isMouseSqueaking
|
||||||
|
? 'mouseSqueak 0.3s ease-in-out'
|
||||||
|
: 'none',
|
||||||
|
}}
|
||||||
|
onClick={handleLogoClick}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
width: '0.0625rem',
|
position: 'absolute',
|
||||||
height: '1.25rem',
|
left: '50%',
|
||||||
backgroundColor: 'var(--mantine-color-default-border)',
|
transform: 'translateX(-50%)',
|
||||||
margin: '0 0.25rem',
|
WebkitAppRegion: 'no-drag',
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
|
{currentScreen === 'interface' && (
|
||||||
|
<Select
|
||||||
|
placeholder="Interface"
|
||||||
|
value={currentTab}
|
||||||
|
onChange={(value) => {
|
||||||
|
if (value === 'eject') {
|
||||||
|
onEject?.();
|
||||||
|
} else {
|
||||||
|
onTabChange?.(value as InterfaceTab);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
data={[
|
||||||
|
{
|
||||||
|
value: 'chat',
|
||||||
|
label: 'Chat',
|
||||||
|
},
|
||||||
|
{ value: 'terminal', label: 'Terminal' },
|
||||||
|
{ value: 'eject', label: 'Eject' },
|
||||||
|
]}
|
||||||
|
allowDeselect={false}
|
||||||
|
variant="unstyled"
|
||||||
|
size="sm"
|
||||||
|
style={{
|
||||||
|
textAlign: 'center',
|
||||||
|
minWidth: '120px',
|
||||||
|
}}
|
||||||
|
styles={{
|
||||||
|
input: {
|
||||||
|
textAlign: 'center',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
border: 'none',
|
||||||
|
fontWeight: 500,
|
||||||
|
userSelect: 'none',
|
||||||
|
cursor: 'pointer',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Group gap="0.125rem" style={{ WebkitAppRegion: 'no-drag' }}>
|
||||||
|
{hasUpdate && (
|
||||||
|
<ActionIcon
|
||||||
|
variant="subtle"
|
||||||
|
color="orange"
|
||||||
|
size="2rem"
|
||||||
|
onClick={openReleasePage}
|
||||||
|
aria-label="New release available"
|
||||||
|
style={{
|
||||||
|
borderRadius: '0.25rem',
|
||||||
|
margin: '0.125rem',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CircleFadingArrowUp size="1.25rem" />
|
||||||
|
</ActionIcon>
|
||||||
|
)}
|
||||||
|
|
||||||
{[
|
|
||||||
{
|
|
||||||
icon: <Minus size="1rem" />,
|
|
||||||
onClick: handleMinimize,
|
|
||||||
color: undefined,
|
|
||||||
label: 'Minimize window',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: isMaximized ? <Copy size="1rem" /> : <Square size="1rem" />,
|
|
||||||
onClick: handleMaximize,
|
|
||||||
color: undefined,
|
|
||||||
label: isMaximized ? 'Restore window' : 'Maximize window',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: <X size="1.25rem" />,
|
|
||||||
onClick: handleClose,
|
|
||||||
color: 'red' as const,
|
|
||||||
label: 'Close window',
|
|
||||||
},
|
|
||||||
].map((button, index) => (
|
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
key={index}
|
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
size="2rem"
|
size="2rem"
|
||||||
onClick={button.onClick}
|
onClick={onOpenSettings}
|
||||||
color={button.color}
|
aria-label="Open settings"
|
||||||
aria-label={button.label}
|
|
||||||
style={{
|
style={{
|
||||||
borderRadius: '0.25rem',
|
borderRadius: '0.25rem',
|
||||||
margin: '0.125rem',
|
margin: '0.125rem',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{button.icon}
|
<Settings size="1.25rem" />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
))}
|
|
||||||
</Group>
|
<Box
|
||||||
</Box>
|
style={{
|
||||||
|
width: '0.0625rem',
|
||||||
|
height: '1.25rem',
|
||||||
|
backgroundColor: 'var(--mantine-color-default-border)',
|
||||||
|
margin: '0 0.25rem',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{[
|
||||||
|
{
|
||||||
|
icon: <Minus size="1rem" />,
|
||||||
|
onClick: handleMinimize,
|
||||||
|
color: undefined,
|
||||||
|
label: 'Minimize window',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: isMaximized ? <Copy size="1rem" /> : <Square size="1rem" />,
|
||||||
|
onClick: handleMaximize,
|
||||||
|
color: undefined,
|
||||||
|
label: isMaximized ? 'Restore window' : 'Maximize window',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <X size="1.25rem" />,
|
||||||
|
onClick: handleClose,
|
||||||
|
color: 'red' as const,
|
||||||
|
label: 'Close window',
|
||||||
|
},
|
||||||
|
].map((button, index) => (
|
||||||
|
<ActionIcon
|
||||||
|
key={index}
|
||||||
|
variant="subtle"
|
||||||
|
size="2rem"
|
||||||
|
onClick={button.onClick}
|
||||||
|
color={button.color}
|
||||||
|
aria-label={button.label}
|
||||||
|
style={{
|
||||||
|
borderRadius: '0.25rem',
|
||||||
|
margin: '0.125rem',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{button.icon}
|
||||||
|
</ActionIcon>
|
||||||
|
))}
|
||||||
|
</Group>
|
||||||
|
</Box>
|
||||||
|
</AppShell.Header>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import { ChevronDown } from 'lucide-react';
|
||||||
import styles from '@/styles/layout.module.css';
|
import styles from '@/styles/layout.module.css';
|
||||||
import { SERVER_READY_SIGNALS } from '@/constants';
|
import { SERVER_READY_SIGNALS } from '@/constants';
|
||||||
import { handleTerminalOutput, processTerminalContent } from '@/utils/terminal';
|
import { handleTerminalOutput, processTerminalContent } from '@/utils/terminal';
|
||||||
import { useLaunchConfigStore } from '@/stores/launchConfigStore';
|
import { useLaunchConfigStore } from '@/stores/launchConfig';
|
||||||
import type { FrontendPreference } from '@/types';
|
import type { FrontendPreference } from '@/types';
|
||||||
|
|
||||||
interface TerminalTabProps {
|
interface TerminalTabProps {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,17 @@
|
||||||
import { Stack, Group, Text, TextInput, NumberInput } from '@mantine/core';
|
import {
|
||||||
|
Stack,
|
||||||
|
Group,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
NumberInput,
|
||||||
|
Button,
|
||||||
|
} 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';
|
||||||
|
import { CommandLineArgumentsModal } from '@/components/CommandLineArgumentsModal';
|
||||||
import { useLaunchConfig } from '@/hooks/useLaunchConfig';
|
import { useLaunchConfig } from '@/hooks/useLaunchConfig';
|
||||||
|
import { useModalStore } from '@/stores/modal';
|
||||||
import { safeExecute } from '@/utils/logger';
|
import { safeExecute } from '@/utils/logger';
|
||||||
|
|
||||||
export const AdvancedTab = () => {
|
export const AdvancedTab = () => {
|
||||||
|
|
@ -29,12 +38,21 @@ export const AdvancedTab = () => {
|
||||||
handleMoecpuChange,
|
handleMoecpuChange,
|
||||||
handleMoeexpertsChange,
|
handleMoeexpertsChange,
|
||||||
} = useLaunchConfig();
|
} = useLaunchConfig();
|
||||||
|
const { modals, setModalOpen } = useModalStore();
|
||||||
const [backendSupport, setBackendSupport] = useState<{
|
const [backendSupport, setBackendSupport] = useState<{
|
||||||
noavx2: boolean;
|
noavx2: boolean;
|
||||||
failsafe: boolean;
|
failsafe: boolean;
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
|
const handleAddArgument = (newArgument: string) => {
|
||||||
|
const currentArgs = additionalArguments.trim();
|
||||||
|
const updatedArgs = currentArgs
|
||||||
|
? `${currentArgs} ${newArgument}`
|
||||||
|
: newArgument;
|
||||||
|
handleAdditionalArgumentsChange(updatedArgs);
|
||||||
|
};
|
||||||
|
|
||||||
const isGpuBackend = backend === 'cuda' || backend === 'rocm';
|
const isGpuBackend = backend === 'cuda' || backend === 'rocm';
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -62,11 +80,6 @@ export const AdvancedTab = () => {
|
||||||
return (
|
return (
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
<div>
|
<div>
|
||||||
<Group gap="xs" align="center" mb="md">
|
|
||||||
<Text size="sm" fw={600}>
|
|
||||||
Performance Options
|
|
||||||
</Text>
|
|
||||||
</Group>
|
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
<Group gap="lg" align="flex-start" wrap="nowrap">
|
<Group gap="lg" align="flex-start" wrap="nowrap">
|
||||||
<CheckboxWithTooltip
|
<CheckboxWithTooltip
|
||||||
|
|
@ -155,11 +168,6 @@ export const AdvancedTab = () => {
|
||||||
</div>
|
</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">
|
<Stack gap="md">
|
||||||
<Group gap="lg" align="flex-start" wrap="nowrap">
|
<Group gap="lg" align="flex-start" wrap="nowrap">
|
||||||
<div style={{ flex: 1, minWidth: 200 }}>
|
<div style={{ flex: 1, minWidth: 200 }}>
|
||||||
|
|
@ -204,11 +212,20 @@ export const AdvancedTab = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Group gap="xs" align="center" mb="xs">
|
<Group mb="md" justify="space-between">
|
||||||
<Text size="sm" fw={500}>
|
<Group>
|
||||||
Additional arguments
|
<Text size="sm" fw={500}>
|
||||||
</Text>
|
Additional arguments
|
||||||
<InfoTooltip label="Additional command line arguments to pass to the KoboldCPP binary. Leave this empty if you don't know what they are." />
|
</Text>
|
||||||
|
<InfoTooltip label="Additional command line arguments to pass to the KoboldCPP binary. Leave this empty if you don't know what they are." />
|
||||||
|
</Group>
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
variant="light"
|
||||||
|
onClick={() => setModalOpen('commandLineArguments', true)}
|
||||||
|
>
|
||||||
|
View Available Arguments
|
||||||
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
<TextInput
|
<TextInput
|
||||||
placeholder="Additional command line arguments"
|
placeholder="Additional command line arguments"
|
||||||
|
|
@ -218,6 +235,12 @@ export const AdvancedTab = () => {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<CommandLineArgumentsModal
|
||||||
|
opened={modals.commandLineArguments}
|
||||||
|
onClose={() => setModalOpen('commandLineArguments', false)}
|
||||||
|
onAddArgument={handleAddArgument}
|
||||||
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -314,7 +314,7 @@ export const LaunchScreen = ({
|
||||||
onChange={setActiveTab}
|
onChange={setActiveTab}
|
||||||
styles={{
|
styles={{
|
||||||
root: {
|
root: {
|
||||||
maxHeight: '24rem',
|
maxHeight: '22rem',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useLaunchConfigStore } from '@/stores/launchConfigStore';
|
import { useLaunchConfigStore } from '@/stores/launchConfig';
|
||||||
import {
|
import {
|
||||||
type ImageModelPreset,
|
type ImageModelPreset,
|
||||||
IMAGE_MODEL_PRESETS,
|
IMAGE_MODEL_PRESETS,
|
||||||
|
|
|
||||||
35
src/stores/modal.ts
Normal file
35
src/stores/modal.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { create } from 'zustand';
|
||||||
|
|
||||||
|
interface ModalState {
|
||||||
|
modals: {
|
||||||
|
settings: boolean;
|
||||||
|
ejectConfirm: boolean;
|
||||||
|
updateAvailable: boolean;
|
||||||
|
commandLineArguments: boolean;
|
||||||
|
};
|
||||||
|
setModalOpen: (
|
||||||
|
modalName: keyof ModalState['modals'],
|
||||||
|
isOpen: boolean
|
||||||
|
) => void;
|
||||||
|
isAnyModalOpen: () => boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useModalStore = create<ModalState>((set, get) => ({
|
||||||
|
modals: {
|
||||||
|
settings: false,
|
||||||
|
ejectConfirm: false,
|
||||||
|
updateAvailable: false,
|
||||||
|
commandLineArguments: false,
|
||||||
|
},
|
||||||
|
setModalOpen: (modalName, isOpen) =>
|
||||||
|
set((state) => ({
|
||||||
|
modals: {
|
||||||
|
...state.modals,
|
||||||
|
[modalName]: isOpen,
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
isAnyModalOpen: () => {
|
||||||
|
const { modals } = get();
|
||||||
|
return Object.values(modals).some(Boolean);
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
@ -1,5 +1,23 @@
|
||||||
@import '@mantine/core/styles.css';
|
@import '@mantine/core/styles.css';
|
||||||
|
|
||||||
|
.mantine-AppShell-main {
|
||||||
|
overflow: auto !important;
|
||||||
|
height: calc(100vh - 2.5rem) !important;
|
||||||
|
position: relative !important;
|
||||||
|
top: 2.5rem !important;
|
||||||
|
margin-top: 0 !important;
|
||||||
|
padding-top: 1rem !important;
|
||||||
|
padding-bottom: 0 !important;
|
||||||
|
min-height: calc(100vh - 2.5rem) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mantine-AppShell-root {
|
||||||
|
--app-shell-header-offset: 2.5rem;
|
||||||
|
--app-shell-navbar-offset: 0rem;
|
||||||
|
--app-shell-aside-offset: 0rem;
|
||||||
|
--app-shell-footer-offset: 0rem;
|
||||||
|
}
|
||||||
|
|
||||||
/* TODO: something (mantine?) is setting this sometimes to "none" on the terminal tab */
|
/* TODO: something (mantine?) is setting this sometimes to "none" on the terminal tab */
|
||||||
body {
|
body {
|
||||||
user-select: auto !important;
|
user-select: auto !important;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue