mirror of
https://github.com/lone-cloud/gerbil
synced 2026-06-04 04:04:44 -07:00
settings icon back to the titlebar, fix statusbar-related layout issues
This commit is contained in:
parent
dc922b333d
commit
96b1ecc7be
11 changed files with 225 additions and 212 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "gerbil",
|
"name": "gerbil",
|
||||||
"productName": "Gerbil",
|
"productName": "Gerbil",
|
||||||
"version": "1.1.2",
|
"version": "1.2.0",
|
||||||
"description": "Run Large Language Models locally",
|
"description": "Run Large Language Models locally",
|
||||||
"main": "out/main/index.js",
|
"main": "out/main/index.js",
|
||||||
"homepage": "./",
|
"homepage": "./",
|
||||||
|
|
|
||||||
25
src/App.tsx
25
src/App.tsx
|
|
@ -25,6 +25,7 @@ export const App = () => {
|
||||||
const [frontendPreference, setFrontendPreference] =
|
const [frontendPreference, setFrontendPreference] =
|
||||||
useState<FrontendPreference>('koboldcpp');
|
useState<FrontendPreference>('koboldcpp');
|
||||||
const [ejectConfirmModalOpen, setEjectConfirmModalOpen] = useState(false);
|
const [ejectConfirmModalOpen, setEjectConfirmModalOpen] = useState(false);
|
||||||
|
const isInterfaceScreen = currentScreen === 'interface';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
updateInfo: binaryUpdateInfo,
|
updateInfo: binaryUpdateInfo,
|
||||||
|
|
@ -146,7 +147,7 @@ export const App = () => {
|
||||||
<AppShell
|
<AppShell
|
||||||
header={{ height: TITLEBAR_HEIGHT }}
|
header={{ height: TITLEBAR_HEIGHT }}
|
||||||
footer={{ height: STATUSBAR_HEIGHT }}
|
footer={{ height: STATUSBAR_HEIGHT }}
|
||||||
padding={currentScreen === 'interface' ? 0 : 'md'}
|
padding={isInterfaceScreen ? 0 : 'md'}
|
||||||
>
|
>
|
||||||
<TitleBar
|
<TitleBar
|
||||||
currentScreen={currentScreen || 'welcome'}
|
currentScreen={currentScreen || 'welcome'}
|
||||||
|
|
@ -154,9 +155,21 @@ export const App = () => {
|
||||||
onTabChange={setActiveInterfaceTab}
|
onTabChange={setActiveInterfaceTab}
|
||||||
onEject={handleEject}
|
onEject={handleEject}
|
||||||
frontendPreference={frontendPreference}
|
frontendPreference={frontendPreference}
|
||||||
|
onFrontendPreferenceChange={setFrontendPreference}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<AppShell.Main>
|
<AppShell.Main
|
||||||
|
top={TITLEBAR_HEIGHT}
|
||||||
|
style={{
|
||||||
|
height: `calc(100vh - ${TITLEBAR_HEIGHT} - ${STATUSBAR_HEIGHT})`,
|
||||||
|
minHeight: `calc(100vh - ${TITLEBAR_HEIGHT} - ${STATUSBAR_HEIGHT})`,
|
||||||
|
position: 'relative',
|
||||||
|
overflow: 'auto',
|
||||||
|
paddingTop: 0,
|
||||||
|
top: TITLEBAR_HEIGHT,
|
||||||
|
paddingBottom: isInterfaceScreen ? 0 : '1rem',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
{currentScreen === null ? (
|
{currentScreen === null ? (
|
||||||
<Center h="100%" style={{ minHeight: '25rem' }}>
|
<Center h="100%" style={{ minHeight: '25rem' }}>
|
||||||
|
|
@ -191,7 +204,7 @@ export const App = () => {
|
||||||
</ScreenTransition>
|
</ScreenTransition>
|
||||||
|
|
||||||
<ScreenTransition
|
<ScreenTransition
|
||||||
isActive={currentScreen === 'interface'}
|
isActive={isInterfaceScreen}
|
||||||
shouldAnimate={hasInitialized}
|
shouldAnimate={hasInitialized}
|
||||||
>
|
>
|
||||||
<InterfaceScreen
|
<InterfaceScreen
|
||||||
|
|
@ -220,11 +233,7 @@ export const App = () => {
|
||||||
</AppShell.Main>
|
</AppShell.Main>
|
||||||
|
|
||||||
<AppShell.Footer>
|
<AppShell.Footer>
|
||||||
<StatusBar
|
<StatusBar />
|
||||||
currentScreen={currentScreen}
|
|
||||||
frontendPreference={frontendPreference}
|
|
||||||
onFrontendPreferenceChange={setFrontendPreference}
|
|
||||||
/>
|
|
||||||
</AppShell.Footer>
|
</AppShell.Footer>
|
||||||
|
|
||||||
<EjectConfirmModal
|
<EjectConfirmModal
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import {
|
||||||
Code,
|
Code,
|
||||||
TextInput,
|
TextInput,
|
||||||
Button,
|
Button,
|
||||||
|
Box,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
|
@ -543,68 +544,6 @@ export const CommandLineArgumentsModal = ({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
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: '0.875em', fontWeight: 600 }}>
|
|
||||||
{arg.flag}
|
|
||||||
</Code>
|
|
||||||
{arg.aliases &&
|
|
||||||
arg.aliases.map((alias) => (
|
|
||||||
<Code key={alias} style={{ fontSize: '0.75em' }} 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 (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
opened={opened}
|
opened={opened}
|
||||||
|
|
@ -638,11 +577,97 @@ export const CommandLineArgumentsModal = ({
|
||||||
</Group>
|
</Group>
|
||||||
</Accordion.Control>
|
</Accordion.Control>
|
||||||
<Accordion.Panel>
|
<Accordion.Panel>
|
||||||
<Stack gap="md">{args.map(renderArgument)}</Stack>
|
<Stack gap="md">
|
||||||
|
{args.map((arg) => (
|
||||||
|
<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: '0.875em', fontWeight: 600 }}
|
||||||
|
>
|
||||||
|
{arg.flag}
|
||||||
|
</Code>
|
||||||
|
{arg.aliases &&
|
||||||
|
arg.aliases.map((alias) => (
|
||||||
|
<Code
|
||||||
|
key={alias}
|
||||||
|
style={{ fontSize: '0.75em' }}
|
||||||
|
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>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
</Accordion.Panel>
|
</Accordion.Panel>
|
||||||
</Accordion.Item>
|
</Accordion.Item>
|
||||||
))}
|
))}
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--mantine-color-body)',
|
||||||
|
padding: '0.5rem 1.5rem 0',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button onClick={onClose} variant="filled">
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,5 @@
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import {
|
import { Badge, Group, Tooltip, useComputedColorScheme } from '@mantine/core';
|
||||||
Box,
|
|
||||||
Flex,
|
|
||||||
ActionIcon,
|
|
||||||
Tooltip,
|
|
||||||
Badge,
|
|
||||||
Group,
|
|
||||||
useComputedColorScheme,
|
|
||||||
} from '@mantine/core';
|
|
||||||
import { Settings } from 'lucide-react';
|
|
||||||
import { SettingsModal } from '@/components/settings/SettingsModal';
|
|
||||||
import { safeExecute } from '@/utils/logger';
|
|
||||||
import type { FrontendPreference, Screen } from '@/types';
|
|
||||||
import type {
|
import type {
|
||||||
CpuMetrics,
|
CpuMetrics,
|
||||||
MemoryMetrics,
|
MemoryMetrics,
|
||||||
|
|
@ -20,23 +8,14 @@ import type {
|
||||||
|
|
||||||
interface StatusBarProps {
|
interface StatusBarProps {
|
||||||
maxDataPoints?: number;
|
maxDataPoints?: number;
|
||||||
currentScreen: Screen | null;
|
|
||||||
frontendPreference: FrontendPreference;
|
|
||||||
onFrontendPreferenceChange: (preference: FrontendPreference) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const StatusBar = ({
|
export const StatusBar = ({ maxDataPoints = 60 }: StatusBarProps) => {
|
||||||
maxDataPoints = 60,
|
|
||||||
currentScreen,
|
|
||||||
frontendPreference: _frontendPreference,
|
|
||||||
onFrontendPreferenceChange,
|
|
||||||
}: StatusBarProps) => {
|
|
||||||
const [cpuMetrics, setCpuMetrics] = useState<CpuMetrics | null>(null);
|
const [cpuMetrics, setCpuMetrics] = useState<CpuMetrics | null>(null);
|
||||||
const [memoryMetrics, setMemoryMetrics] = useState<MemoryMetrics | null>(
|
const [memoryMetrics, setMemoryMetrics] = useState<MemoryMetrics | null>(
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
const [gpuMetrics, setGpuMetrics] = useState<GpuMetrics | null>(null);
|
const [gpuMetrics, setGpuMetrics] = useState<GpuMetrics | null>(null);
|
||||||
const [settingsModalOpen, setSettingsModalOpen] = useState(false);
|
|
||||||
const colorScheme = useComputedColorScheme('light', {
|
const colorScheme = useComputedColorScheme('light', {
|
||||||
getInitialValueInEffect: true,
|
getInitialValueInEffect: true,
|
||||||
});
|
});
|
||||||
|
|
@ -74,8 +53,10 @@ export const StatusBar = ({
|
||||||
}, [maxDataPoints]);
|
}, [maxDataPoints]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Group
|
||||||
px="xs"
|
px="xs"
|
||||||
|
gap="xs"
|
||||||
|
justify="flex-end"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
colorScheme === 'dark'
|
colorScheme === 'dark'
|
||||||
|
|
@ -83,94 +64,59 @@ export const StatusBar = ({
|
||||||
: 'var(--mantine-color-gray-1)',
|
: 'var(--mantine-color-gray-1)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Flex align="center" justify="space-between">
|
{cpuMetrics && memoryMetrics && (
|
||||||
<Group gap="xs">
|
<>
|
||||||
{cpuMetrics && memoryMetrics && (
|
<Tooltip label={`${cpuMetrics.usage.toFixed(1)}%`} position="top">
|
||||||
<>
|
<Badge
|
||||||
<Tooltip label={`${cpuMetrics.usage.toFixed(1)}%`} position="top">
|
size="sm"
|
||||||
<Badge
|
variant="light"
|
||||||
size="sm"
|
style={{ minWidth: '5rem', textAlign: 'center' }}
|
||||||
variant="light"
|
>
|
||||||
style={{ minWidth: '5rem', textAlign: 'center' }}
|
CPU: {cpuMetrics.usage.toFixed(1)}%
|
||||||
>
|
</Badge>
|
||||||
CPU: {cpuMetrics.usage.toFixed(1)}%
|
</Tooltip>
|
||||||
</Badge>
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
<Tooltip
|
<Tooltip
|
||||||
label={`${memoryMetrics.used.toFixed(1)} GB / ${memoryMetrics.total.toFixed(1)} GB (${memoryMetrics.usage.toFixed(1)}%)`}
|
label={`${memoryMetrics.used.toFixed(1)} GB / ${memoryMetrics.total.toFixed(1)} GB (${memoryMetrics.usage.toFixed(1)}%)`}
|
||||||
position="top"
|
position="top"
|
||||||
>
|
|
||||||
<Badge
|
|
||||||
size="sm"
|
|
||||||
variant="light"
|
|
||||||
style={{ minWidth: '5rem', textAlign: 'center' }}
|
|
||||||
>
|
|
||||||
RAM: {memoryMetrics.usage.toFixed(1)}%
|
|
||||||
</Badge>
|
|
||||||
</Tooltip>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{gpuMetrics?.gpus.map((gpu, index) => (
|
|
||||||
<Group gap="xs" key={`gpu-${index}`}>
|
|
||||||
<Tooltip label={`${gpu.usage.toFixed(1)}%`} position="top">
|
|
||||||
<Badge
|
|
||||||
size="sm"
|
|
||||||
variant="light"
|
|
||||||
style={{ minWidth: '5rem', textAlign: 'center' }}
|
|
||||||
>
|
|
||||||
GPU: {gpu.usage.toFixed(1)}%
|
|
||||||
</Badge>
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
<Tooltip
|
|
||||||
label={`${gpu.memoryUsed.toFixed(1)} GB / ${gpu.memoryTotal.toFixed(1)} GB (${gpu.memoryUsage.toFixed(1)}%)`}
|
|
||||||
position="top"
|
|
||||||
>
|
|
||||||
<Badge
|
|
||||||
size="sm"
|
|
||||||
variant="light"
|
|
||||||
style={{ minWidth: '5rem', textAlign: 'center' }}
|
|
||||||
>
|
|
||||||
VRAM: {gpu.memoryUsage.toFixed(1)}%
|
|
||||||
</Badge>
|
|
||||||
</Tooltip>
|
|
||||||
</Group>
|
|
||||||
))}
|
|
||||||
</Group>
|
|
||||||
|
|
||||||
<Tooltip label="Settings" position="top">
|
|
||||||
<ActionIcon
|
|
||||||
variant="subtle"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => setSettingsModalOpen(true)}
|
|
||||||
aria-label="Open settings"
|
|
||||||
style={{
|
|
||||||
borderRadius: '0.25rem',
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Settings size="1rem" />
|
<Badge
|
||||||
</ActionIcon>
|
size="sm"
|
||||||
</Tooltip>
|
variant="light"
|
||||||
</Flex>
|
style={{ minWidth: '5rem', textAlign: 'center' }}
|
||||||
|
>
|
||||||
|
RAM: {memoryMetrics.usage.toFixed(1)}%
|
||||||
|
</Badge>
|
||||||
|
</Tooltip>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
<SettingsModal
|
{gpuMetrics?.gpus.map((gpu, index) => (
|
||||||
isOnInterfaceScreen={currentScreen === 'interface'}
|
<Group gap="xs" key={`gpu-${index}`}>
|
||||||
opened={settingsModalOpen}
|
<Tooltip label={`${gpu.usage.toFixed(1)}%`} position="top">
|
||||||
onClose={async () => {
|
<Badge
|
||||||
setSettingsModalOpen(false);
|
size="sm"
|
||||||
const preference = await safeExecute(
|
variant="light"
|
||||||
() =>
|
style={{ minWidth: '5rem', textAlign: 'center' }}
|
||||||
window.electronAPI.config.get(
|
>
|
||||||
'frontendPreference'
|
GPU: {gpu.usage.toFixed(1)}%
|
||||||
) as Promise<FrontendPreference>,
|
</Badge>
|
||||||
'Failed to load frontend preference:'
|
</Tooltip>
|
||||||
);
|
|
||||||
onFrontendPreferenceChange(preference || 'koboldcpp');
|
<Tooltip
|
||||||
}}
|
label={`${gpu.memoryUsed.toFixed(1)} GB / ${gpu.memoryTotal.toFixed(1)} GB (${gpu.memoryUsage.toFixed(1)}%)`}
|
||||||
currentScreen={currentScreen || undefined}
|
position="top"
|
||||||
/>
|
>
|
||||||
</Box>
|
<Badge
|
||||||
|
size="sm"
|
||||||
|
variant="light"
|
||||||
|
style={{ minWidth: '5rem', textAlign: 'center' }}
|
||||||
|
>
|
||||||
|
VRAM: {gpu.memoryUsage.toFixed(1)}%
|
||||||
|
</Badge>
|
||||||
|
</Tooltip>
|
||||||
|
</Group>
|
||||||
|
))}
|
||||||
|
</Group>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,20 @@ import {
|
||||||
useComputedColorScheme,
|
useComputedColorScheme,
|
||||||
AppShell,
|
AppShell,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { Minus, Square, X, Copy, CircleFadingArrowUp } from 'lucide-react';
|
import {
|
||||||
|
Minus,
|
||||||
|
Square,
|
||||||
|
X,
|
||||||
|
Copy,
|
||||||
|
CircleFadingArrowUp,
|
||||||
|
Settings,
|
||||||
|
} from 'lucide-react';
|
||||||
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 { useLaunchConfigStore } from '@/stores/launchConfig';
|
import { useLaunchConfigStore } from '@/stores/launchConfig';
|
||||||
|
import { SettingsModal } from '@/components/settings/SettingsModal';
|
||||||
|
import { safeExecute } from '@/utils/logger';
|
||||||
import iconUrl from '/icon.png';
|
import iconUrl from '/icon.png';
|
||||||
import { FRONTENDS, PRODUCT_NAME, TITLEBAR_HEIGHT } from '@/constants';
|
import { FRONTENDS, PRODUCT_NAME, TITLEBAR_HEIGHT } from '@/constants';
|
||||||
import type { FrontendPreference, InterfaceTab, Screen } from '@/types';
|
import type { FrontendPreference, InterfaceTab, Screen } from '@/types';
|
||||||
|
|
@ -22,6 +31,7 @@ interface TitleBarProps {
|
||||||
onTabChange: (tab: InterfaceTab) => void;
|
onTabChange: (tab: InterfaceTab) => void;
|
||||||
onEject: () => void;
|
onEject: () => void;
|
||||||
frontendPreference: FrontendPreference;
|
frontendPreference: FrontendPreference;
|
||||||
|
onFrontendPreferenceChange: (preference: FrontendPreference) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TitleBar = ({
|
export const TitleBar = ({
|
||||||
|
|
@ -30,6 +40,7 @@ export const TitleBar = ({
|
||||||
onTabChange,
|
onTabChange,
|
||||||
onEject,
|
onEject,
|
||||||
frontendPreference,
|
frontendPreference,
|
||||||
|
onFrontendPreferenceChange,
|
||||||
}: TitleBarProps) => {
|
}: TitleBarProps) => {
|
||||||
const computedColorScheme = useComputedColorScheme('light', {
|
const computedColorScheme = useComputedColorScheme('light', {
|
||||||
getInitialValueInEffect: true,
|
getInitialValueInEffect: true,
|
||||||
|
|
@ -41,6 +52,7 @@ export const TitleBar = ({
|
||||||
const [isMouseSqueaking, setIsMouseSqueaking] = useState(false);
|
const [isMouseSqueaking, setIsMouseSqueaking] = useState(false);
|
||||||
const [isMaximized, setIsMaximized] = useState(false);
|
const [isMaximized, setIsMaximized] = useState(false);
|
||||||
const [isSelectOpen, setIsSelectOpen] = useState(false);
|
const [isSelectOpen, setIsSelectOpen] = useState(false);
|
||||||
|
const [settingsModalOpen, setSettingsModalOpen] = useState(false);
|
||||||
|
|
||||||
const handleMinimize = () => {
|
const handleMinimize = () => {
|
||||||
window.electronAPI.app.minimizeWindow();
|
window.electronAPI.app.minimizeWindow();
|
||||||
|
|
@ -225,6 +237,29 @@ export const TitleBar = ({
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<ActionIcon
|
||||||
|
variant="subtle"
|
||||||
|
size={TITLEBAR_HEIGHT}
|
||||||
|
onClick={() => setSettingsModalOpen(true)}
|
||||||
|
aria-label="Open settings"
|
||||||
|
tabIndex={-1}
|
||||||
|
style={{
|
||||||
|
borderRadius: 0,
|
||||||
|
margin: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Settings size="1.25rem" />
|
||||||
|
</ActionIcon>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
style={{
|
||||||
|
width: '0.0625rem',
|
||||||
|
height: '1.25rem',
|
||||||
|
backgroundColor: 'var(--mantine-color-default-border)',
|
||||||
|
margin: '0 0.25rem',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
{[
|
{[
|
||||||
{
|
{
|
||||||
icon: <Minus size="1rem" />,
|
icon: <Minus size="1rem" />,
|
||||||
|
|
@ -263,6 +298,23 @@ export const TitleBar = ({
|
||||||
))}
|
))}
|
||||||
</Group>
|
</Group>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
<SettingsModal
|
||||||
|
isOnInterfaceScreen={currentScreen === 'interface'}
|
||||||
|
opened={settingsModalOpen}
|
||||||
|
onClose={async () => {
|
||||||
|
setSettingsModalOpen(false);
|
||||||
|
const preference = await safeExecute(
|
||||||
|
() =>
|
||||||
|
window.electronAPI.config.get(
|
||||||
|
'frontendPreference'
|
||||||
|
) as Promise<FrontendPreference>,
|
||||||
|
'Failed to load frontend preference:'
|
||||||
|
);
|
||||||
|
onFrontendPreferenceChange(preference || 'koboldcpp');
|
||||||
|
}}
|
||||||
|
currentScreen={currentScreen || undefined}
|
||||||
|
/>
|
||||||
</AppShell.Header>
|
</AppShell.Header>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import {
|
||||||
OPENWEBUI,
|
OPENWEBUI,
|
||||||
FRONTENDS,
|
FRONTENDS,
|
||||||
TITLEBAR_HEIGHT,
|
TITLEBAR_HEIGHT,
|
||||||
|
STATUSBAR_HEIGHT,
|
||||||
} from '@/constants';
|
} from '@/constants';
|
||||||
import { useLaunchConfigStore } from '@/stores/launchConfig';
|
import { useLaunchConfigStore } from '@/stores/launchConfig';
|
||||||
import type { FrontendPreference } from '@/types';
|
import type { FrontendPreference } from '@/types';
|
||||||
|
|
@ -65,7 +66,7 @@ export const ServerTab = ({
|
||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: `calc(100vh - ${TITLEBAR_HEIGHT})`,
|
height: `calc(100vh - ${TITLEBAR_HEIGHT} - ${STATUSBAR_HEIGHT})`,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,11 @@ import {
|
||||||
useComputedColorScheme,
|
useComputedColorScheme,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { ChevronDown } from 'lucide-react';
|
import { ChevronDown } from 'lucide-react';
|
||||||
import { SERVER_READY_SIGNALS, TITLEBAR_HEIGHT } from '@/constants';
|
import {
|
||||||
|
SERVER_READY_SIGNALS,
|
||||||
|
STATUSBAR_HEIGHT,
|
||||||
|
TITLEBAR_HEIGHT,
|
||||||
|
} from '@/constants';
|
||||||
import { handleTerminalOutput, processTerminalContent } from '@/utils/terminal';
|
import { handleTerminalOutput, processTerminalContent } from '@/utils/terminal';
|
||||||
import { useLaunchConfigStore } from '@/stores/launchConfig';
|
import { useLaunchConfigStore } from '@/stores/launchConfig';
|
||||||
import type { FrontendPreference } from '@/types';
|
import type { FrontendPreference } from '@/types';
|
||||||
|
|
@ -127,7 +131,7 @@ export const TerminalTab = forwardRef<TerminalTabRef, TerminalTabProps>(
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
height: `calc(100vh - ${TITLEBAR_HEIGHT})`,
|
height: `calc(100vh - ${TITLEBAR_HEIGHT} - ${STATUSBAR_HEIGHT})`,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
backgroundColor: isDark
|
backgroundColor: isDark
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ export const BackendSelector = () => {
|
||||||
<Text size="sm" fw={500}>
|
<Text size="sm" fw={500}>
|
||||||
Backend
|
Backend
|
||||||
</Text>
|
</Text>
|
||||||
<InfoTooltip label="Select a backend to use. CUDA runs on NVIDIA GPUs, and is much faster. ROCm is the AMD equivalent. Vulkan and CLBlast work on all GPUs." />
|
<InfoTooltip label="Select a backend to use to run LLMs. CUDA runs on NVIDIA GPUs, and is much faster. ROCm is the AMD equivalent. Vulkan and CLBlast work on all GPUs." />
|
||||||
</Group>
|
</Group>
|
||||||
<Select
|
<Select
|
||||||
placeholder={
|
placeholder={
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ export const GeneralTab = ({
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
value: 'koboldcpp',
|
value: 'koboldcpp',
|
||||||
label: 'Built-in Interface',
|
label: 'Built-in',
|
||||||
badges: ['Text', 'Image'],
|
badges: ['Text', 'Image'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,6 @@ export const SettingsModal = ({
|
||||||
styles={{
|
styles={{
|
||||||
...MODAL_STYLES_WITH_TITLEBAR,
|
...MODAL_STYLES_WITH_TITLEBAR,
|
||||||
content: {
|
content: {
|
||||||
...MODAL_STYLES_WITH_TITLEBAR.content,
|
|
||||||
paddingBottom: 0,
|
paddingBottom: 0,
|
||||||
},
|
},
|
||||||
body: {
|
body: {
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,5 @@
|
||||||
@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: 0 !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 */
|
|
||||||
body {
|
|
||||||
user-select: auto !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Custom scrollbars */
|
/* Custom scrollbars */
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 0.5rem;
|
width: 0.5rem;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue