more strict type-based linting, ensure that mispelled words are added to the context menu for replacement

This commit is contained in:
lone-cloud 2025-12-30 21:23:49 -08:00
parent c728cf0205
commit b5b7310958
52 changed files with 278 additions and 206 deletions

View file

@ -1,3 +1,4 @@
import { defineConfig } from 'eslint/config';
import js from '@eslint/js'; import js from '@eslint/js';
import globals from 'globals'; import globals from 'globals';
import tseslint from '@typescript-eslint/eslint-plugin'; import tseslint from '@typescript-eslint/eslint-plugin';
@ -11,7 +12,7 @@ import noComments from 'eslint-plugin-no-comments';
// @ts-ignore - No types available // @ts-ignore - No types available
import promise from 'eslint-plugin-promise'; import promise from 'eslint-plugin-promise';
const config = [ const config = defineConfig([
js.configs.recommended, js.configs.recommended,
{ {
ignores: [ ignores: [
@ -50,8 +51,8 @@ const config = [
}, },
}, },
plugins: { plugins: {
'@typescript-eslint': tseslint, '@typescript-eslint': tseslint as any,
'react-hooks': reactHooks, 'react-hooks': reactHooks as any,
react, react,
sonarjs, sonarjs,
'no-comments': noComments, 'no-comments': noComments,
@ -69,6 +70,7 @@ const config = [
...importPlugin.configs.recommended.rules, ...importPlugin.configs.recommended.rules,
...importPlugin.configs.typescript.rules, ...importPlugin.configs.typescript.rules,
...tseslint.configs.recommended.rules, ...tseslint.configs.recommended.rules,
...tseslint.configs['recommended-type-checked'].rules,
...tseslint.configs.stylistic.rules, ...tseslint.configs.stylistic.rules,
'@typescript-eslint/no-unused-vars': [ '@typescript-eslint/no-unused-vars': [
@ -82,6 +84,12 @@ const config = [
'no-unused-vars': 'off', 'no-unused-vars': 'off',
'@typescript-eslint/no-explicit-any': 'warn', '@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-unsafe-argument': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-unsafe-return': 'off',
'react/function-component-definition': [ 'react/function-component-definition': [
'error', 'error',
{ {
@ -226,6 +234,6 @@ const config = [
'import/no-default-export': 'off', 'import/no-default-export': 'off',
}, },
}, },
]; ]);
export default config; export default config;

View file

@ -1,7 +1,7 @@
{ {
"name": "gerbil", "name": "gerbil",
"productName": "Gerbil", "productName": "Gerbil",
"version": "1.18.1", "version": "1.18.2",
"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

@ -38,7 +38,7 @@ const ErrorFallback = ({
leftSection={ leftSection={
<FolderOpen style={{ width: rem(16), height: rem(16) }} /> <FolderOpen style={{ width: rem(16), height: rem(16) }} />
} }
onClick={() => window.electronAPI.app.showLogsFolder()} onClick={() => void window.electronAPI.app.showLogsFolder()}
> >
Show Logs Folder Show Logs Folder
</Button> </Button>
@ -51,7 +51,7 @@ const ErrorFallback = ({
<Button <Button
onClick={() => { onClick={() => {
window.electronAPI?.app?.closeWindow(); void window.electronAPI?.app?.closeWindow();
}} }}
variant="subtle" variant="subtle"
color="gray" color="gray"

View file

@ -27,7 +27,11 @@ export const PerformanceBadge = ({
if (iconOnly) { if (iconOnly) {
return ( return (
<Tooltip label={tooltipLabel} position="top"> <Tooltip label={tooltipLabel} position="top">
<ActionIcon size="sm" variant="subtle" onClick={handlePerformanceClick}> <ActionIcon
size="sm"
variant="subtle"
onClick={() => void handlePerformanceClick()}
>
<Activity size="1.125rem" /> <Activity size="1.125rem" />
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
@ -49,7 +53,7 @@ export const PerformanceBadge = ({
fontSize: '0.7em', fontSize: '0.7em',
fontWeight: 500, fontWeight: 500,
}} }}
onClick={handlePerformanceClick} onClick={() => void handlePerformanceClick()}
> >
{label}: {value} {label}: {value}
</Button> </Button>

View file

@ -71,7 +71,7 @@ export const TitleBar = ({
setIsMaximized(currentMaximizedState); setIsMaximized(currentMaximizedState);
}; };
initializeState(); void initializeState();
const cleanup = window.electronAPI.app.onWindowStateToggle(() => const cleanup = window.electronAPI.app.onWindowStateToggle(() =>
setIsMaximized((prev) => !prev) setIsMaximized((prev) => !prev)
@ -112,7 +112,7 @@ export const TitleBar = ({
w={24} w={24}
h={24} h={24}
style={getLogoStyles()} style={getLogoStyles()}
onClick={handleLogoClick} onClick={() => void handleLogoClick()}
/> />
</Group> </Group>
@ -186,19 +186,19 @@ export const TitleBar = ({
{[ {[
{ {
icon: <Minus size="1rem" />, icon: <Minus size="1rem" />,
onClick: () => window.electronAPI.app.minimizeWindow(), onClick: () => void window.electronAPI.app.minimizeWindow(),
color: undefined, color: undefined,
label: 'Minimize window', label: 'Minimize window',
}, },
{ {
icon: isMaximized ? <Copy size="1rem" /> : <Square size="1rem" />, icon: isMaximized ? <Copy size="1rem" /> : <Square size="1rem" />,
onClick: () => window.electronAPI.app.maximizeWindow(), onClick: () => void window.electronAPI.app.maximizeWindow(),
color: undefined, color: undefined,
label: isMaximized ? 'Restore window' : 'Maximize window', label: isMaximized ? 'Restore window' : 'Maximize window',
}, },
{ {
icon: <X size="1.25rem" />, icon: <X size="1.25rem" />,
onClick: () => window.electronAPI.app.closeWindow(), onClick: () => void window.electronAPI.app.closeWindow(),
color: 'red' as const, color: 'red' as const,
label: 'Close window', label: 'Close window',
}, },

View file

@ -154,7 +154,7 @@ export const UpdateAvailableModal = ({
</Button> </Button>
<Button <Button
onClick={handleUpdate} onClick={() => void handleUpdate()}
loading={isDownloading || isUpdating} loading={isDownloading || isUpdating}
disabled={isDownloading || isUpdating} disabled={isDownloading || isUpdating}
leftSection={ leftSection={

View file

@ -36,7 +36,7 @@ export const UpdateButton = () => {
color = 'blue'; color = 'blue';
label = 'Download and install update'; label = 'Download and install update';
onClick = () => { onClick = () => {
downloadUpdate(); void downloadUpdate();
setShowDownload(false); setShowDownload(false);
}; };
icon = <Download size="1.25rem" />; icon = <Download size="1.25rem" />;
@ -47,7 +47,7 @@ export const UpdateButton = () => {
: 'Update available - Click to view'; : 'Update available - Click to view';
onClick = () => { onClick = () => {
if (releaseUrl) { if (releaseUrl) {
window.electronAPI.app.openExternal(releaseUrl); void window.electronAPI.app.openExternal(releaseUrl);
} }
}; };
} }
@ -56,7 +56,7 @@ export const UpdateButton = () => {
e.preventDefault(); e.preventDefault();
if (canAutoUpdate && !isDownloading && !isUpdateDownloaded) { if (canAutoUpdate && !isDownloading && !isUpdateDownloaded) {
setShowDownload(true); setShowDownload(true);
downloadUpdate(); void downloadUpdate();
} }
}; };

View file

@ -84,7 +84,7 @@ export const App = () => {
? displayModel.split('/').pop() || displayModel ? displayModel.split('/').pop() || displayModel
: null; : null;
window.electronAPI.app.updateTrayState({ void window.electronAPI.app.updateTrayState({
screen: currentScreen, screen: currentScreen,
model: modelName, model: modelName,
config: config || null, config: config || null,
@ -158,7 +158,7 @@ export const App = () => {
setHasInitialized(true); setHasInitialized(true);
}; };
checkInstallation(); void checkInstallation();
}, []); }, []);
useEffect(() => { useEffect(() => {
@ -169,12 +169,12 @@ export const App = () => {
await window.electronAPI.kobold.getCurrentBackend(); await window.electronAPI.kobold.getCurrentBackend();
if (currentBackend) { if (currentBackend) {
setTimeout(() => { setTimeout(() => {
checkForUpdates(); void checkForUpdates();
}, 5000); }, 5000);
const interval = setInterval( const interval = setInterval(
() => { () => {
checkForUpdates(); void checkForUpdates();
}, },
6 * 60 * 60 * 1000 6 * 60 * 60 * 1000
); );
@ -183,7 +183,7 @@ export const App = () => {
} }
}; };
runUpdateCheck(); void runUpdateCheck();
}, [loadingRemote, hasInitialized, checkForUpdates]); }, [loadingRemote, hasInitialized, checkForUpdates]);
const handleBinaryUpdate = async (download: DownloadItem) => { const handleBinaryUpdate = async (download: DownloadItem) => {
@ -240,7 +240,7 @@ export const App = () => {
<TitleBar <TitleBar
currentScreen={currentScreen || 'launch'} currentScreen={currentScreen || 'launch'}
currentTab={activeInterfaceTab} currentTab={activeInterfaceTab}
onEject={handleEject} onEject={() => void handleEject()}
onTabChange={setActiveInterfaceTab} onTabChange={setActiveInterfaceTab}
/> />
@ -274,8 +274,8 @@ export const App = () => {
hasInitialized={hasInitialized} hasInitialized={hasInitialized}
activeInterfaceTab={activeInterfaceTab} activeInterfaceTab={activeInterfaceTab}
isServerReady={isServerReady} isServerReady={isServerReady}
onWelcomeComplete={handleWelcomeComplete} onWelcomeComplete={() => void handleWelcomeComplete()}
onDownloadComplete={handleDownloadComplete} onDownloadComplete={() => void handleDownloadComplete()}
onLaunch={handleLaunch} onLaunch={handleLaunch}
/> />
)} )}

View file

@ -53,7 +53,7 @@ export const ImportBackendLink = ({
type="button" type="button"
size="sm" size="sm"
disabled={isDisabled} disabled={isDisabled}
onClick={handleImport} onClick={() => void handleImport()}
> >
{importing ? 'Importing...' : 'Select a local file'} {importing ? 'Importing...' : 'Select a local file'}
</Anchor> </Anchor>

View file

@ -50,7 +50,7 @@ export const InfoCard = ({ title, items, loading = false }: InfoCardProps) => {
<ActionIcon <ActionIcon
variant="subtle" variant="subtle"
size="sm" size="sm"
onClick={copyInfo} onClick={() => void copyInfo()}
aria-label={`Copy ${title}`} aria-label={`Copy ${title}`}
style={{ style={{
position: 'absolute', position: 'absolute',

View file

@ -38,7 +38,7 @@ export const NotepadEditor = ({ tab }: NotepadEditorProps) => {
} }
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
saveTabContent(tab.title, newContent); void saveTabContent(tab.title, newContent);
}, 500); }, 500);
setSaveTimeout(timeout); setSaveTimeout(timeout);

View file

@ -38,7 +38,7 @@ export const NotepadTabs = ({
const handleTabRename = (title: string, newTitle: string) => { const handleTabRename = (title: string, newTitle: string) => {
updateTab(title, { title: newTitle }); updateTab(title, { title: newTitle });
window.electronAPI.notepad.renameTab(title, newTitle); void window.electronAPI.notepad.renameTab(title, newTitle);
}; };
const handleTabBarContextMenu = (e: MouseEvent) => { const handleTabBarContextMenu = (e: MouseEvent) => {
@ -118,7 +118,7 @@ export const NotepadTabs = ({
<ActionIcon <ActionIcon
variant="subtle" variant="subtle"
size="xs" size="xs"
onClick={onCreateNewTab} onClick={() => void onCreateNewTab()}
style={{ style={{
margin: '0.25rem', margin: '0.25rem',
alignSelf: 'center', alignSelf: 'center',

View file

@ -100,7 +100,7 @@ export const DownloadScreen = ({ onDownloadComplete }: DownloadScreenProps) => {
} }
onDownload={(e) => { onDownload={(e) => {
e.stopPropagation(); e.stopPropagation();
handleDownload(download); void handleDownload(download);
}} }}
/> />
</div> </div>

View file

@ -103,7 +103,7 @@ export const ConfigFileManager = ({
return; return;
} }
if (value) { if (value) {
onFileSelection(value); void onFileSelection(value);
} }
}} }}
data={selectData} data={selectData}
@ -124,7 +124,7 @@ export const ConfigFileManager = ({
variant="outline" variant="outline"
leftSection={saveSuccess ? <Check size={14} /> : <Save size={14} />} leftSection={saveSuccess ? <Check size={14} /> : <Save size={14} />}
size="sm" size="sm"
onClick={handleSaveClick} onClick={() => void handleSaveClick()}
color={saveSuccess ? 'green' : undefined} color={saveSuccess ? 'green' : undefined}
style={{ width: '6rem' }} style={{ width: '6rem' }}
> >

View file

@ -60,7 +60,7 @@ export const CreateConfigModal = ({
</Button> </Button>
<Button <Button
disabled={!trimmedConfigName || !!configNameExists} disabled={!trimmedConfigName || !!configNameExists}
onClick={handleSubmit} onClick={() => void handleSubmit()}
> >
Create Create
</Button> </Button>

View file

@ -29,7 +29,7 @@ export const DeleteConfigModal = ({
<Button variant="subtle" onClick={onClose}> <Button variant="subtle" onClick={onClose}>
Cancel Cancel
</Button> </Button>
<Button color="red" onClick={onConfirm}> <Button color="red" onClick={() => void onConfirm()}>
Delete Delete
</Button> </Button>
</Group> </Group>

View file

@ -45,12 +45,12 @@ export const AccelerationSelector = () => {
}; };
if (!hasInitialized.current) { if (!hasInitialized.current) {
loadAccelerations(); void loadAccelerations();
} }
const cleanup = window.electronAPI.kobold.onVersionsUpdated(() => { const cleanup = window.electronAPI.kobold.onVersionsUpdated(() => {
hasInitialized.current = false; hasInitialized.current = false;
loadAccelerations(); void loadAccelerations();
}); });
return cleanup; return cleanup;
@ -133,7 +133,7 @@ export const AccelerationSelector = () => {
} }
}; };
calculateLayers(); void calculateLayers();
}, [ }, [
autoGpuLayers, autoGpuLayers,
model, model,

View file

@ -36,7 +36,7 @@ export const GeneralTab = ({ configLoaded = true }: GeneralTabProps) => {
placeholder="Select a .gguf model file or enter a direct URL to file" placeholder="Select a .gguf model file or enter a direct URL to file"
tooltip="Select a GGUF text generation model file for chat and completion tasks." tooltip="Select a GGUF text generation model file for chat and completion tasks."
onChange={setModel} onChange={setModel}
onSelectFile={() => selectFile('model', 'Select Text Model')} onSelectFile={() => void selectFile('model', 'Select Text Model')}
searchParams={{ searchParams={{
pipelineTag: 'text-generation', pipelineTag: 'text-generation',
filter: 'gguf', filter: 'gguf',

View file

@ -42,7 +42,9 @@ export const ModelCard = ({ modelId }: ModelCardProps) => {
return ( return (
<Accordion> <Accordion>
<Accordion.Item value="readme"> <Accordion.Item value="readme">
<Accordion.Control onClick={loadReadme}>Model Card</Accordion.Control> <Accordion.Control onClick={() => void loadReadme()}>
Model Card
</Accordion.Control>
<Accordion.Panel> <Accordion.Panel>
{loading && ( {loading && (
<Group justify="center" py="md"> <Group justify="center" py="md">

View file

@ -69,7 +69,7 @@ export const ModelsTable = ({
key={model.id} key={model.id}
onClick={() => { onClick={() => {
if (model.gated) { if (model.gated) {
window.electronAPI.app.openExternal( void window.electronAPI.app.openExternal(
`${HUGGINGFACE_BASE_URL}/${model.id}` `${HUGGINGFACE_BASE_URL}/${model.id}`
); );
} else { } else {

View file

@ -71,7 +71,7 @@ export const HuggingFaceSearchModal = ({
useEffect(() => { useEffect(() => {
if (opened && !prevOpenedRef.current) { if (opened && !prevOpenedRef.current) {
searchModels(); void searchModels();
} }
if (!opened && prevOpenedRef.current) { if (!opened && prevOpenedRef.current) {
reset(); reset();
@ -81,7 +81,7 @@ export const HuggingFaceSearchModal = ({
useEffect(() => { useEffect(() => {
if (opened && debouncedQuery !== undefined) { if (opened && debouncedQuery !== undefined) {
searchModels(debouncedQuery); void searchModels(debouncedQuery);
} }
}, [debouncedQuery, opened, searchModels]); }, [debouncedQuery, opened, searchModels]);
@ -91,7 +91,7 @@ export const HuggingFaceSearchModal = ({
}; };
const handleModelSelect = (model: HuggingFaceModelInfo) => { const handleModelSelect = (model: HuggingFaceModelInfo) => {
loadModelFiles(model); void loadModelFiles(model);
}; };
const handleFileSelect = (file: HuggingFaceFileInfo) => { const handleFileSelect = (file: HuggingFaceFileInfo) => {
@ -103,12 +103,12 @@ export const HuggingFaceSearchModal = ({
const handleBack = () => { const handleBack = () => {
reset(); reset();
searchModels(searchQuery); void searchModels(searchQuery);
}; };
const handleOpenExternal = () => { const handleOpenExternal = () => {
if (selectedModel) { if (selectedModel) {
window.electronAPI.app.openExternal( void window.electronAPI.app.openExternal(
`${HUGGINGFACE_BASE_URL}/${selectedModel.id}` `${HUGGINGFACE_BASE_URL}/${selectedModel.id}`
); );
} }
@ -196,7 +196,7 @@ export const HuggingFaceSearchModal = ({
const isNearBottom = const isNearBottom =
target.scrollHeight - y <= target.clientHeight + 100; target.scrollHeight - y <= target.clientHeight + 100;
if (isNearBottom && hasMore && !loading && !selectedModel) { if (isNearBottom && hasMore && !loading && !selectedModel) {
loadMoreModels(); void loadMoreModels();
} }
}} }}
viewportRef={scrollAreaRef} viewportRef={scrollAreaRef}

View file

@ -68,7 +68,7 @@ export const ImageGenerationTab = () => {
placeholder="Select a model file or enter a direct URL" placeholder="Select a model file or enter a direct URL"
tooltip="The primary image generation model. This is the main model that will generate images." tooltip="The primary image generation model. This is the main model that will generate images."
onChange={setSdmodel} onChange={setSdmodel}
onSelectFile={() => selectFile('sdmodel', 'Select Image Model')} onSelectFile={() => void selectFile('sdmodel', 'Select Image Model')}
searchParams={{ searchParams={{
pipelineTag: 'text-to-image', pipelineTag: 'text-to-image',
filter: 'gguf', filter: 'gguf',
@ -84,7 +84,7 @@ export const ImageGenerationTab = () => {
placeholder="Select a T5-XXL encoder file or enter a direct URL" placeholder="Select a T5-XXL encoder file or enter a direct URL"
tooltip="T5-XXL text encoder model for advanced text understanding." tooltip="T5-XXL text encoder model for advanced text understanding."
onChange={setSdt5xxl} onChange={setSdt5xxl}
onSelectFile={() => selectFile('sdt5xxl', 'Select T5XXL Model')} onSelectFile={() => void selectFile('sdt5xxl', 'Select T5XXL Model')}
searchParams={{ searchParams={{
search: 't5xxl', search: 't5xxl',
filter: 'safetensors', filter: 'safetensors',
@ -99,7 +99,7 @@ export const ImageGenerationTab = () => {
placeholder="Select a Clip-L file or enter a direct URL" placeholder="Select a Clip-L file or enter a direct URL"
tooltip="CLIP-L text encoder model for text-image understanding." tooltip="CLIP-L text encoder model for text-image understanding."
onChange={setSdclipl} onChange={setSdclipl}
onSelectFile={() => selectFile('sdclipl', 'Select CLIP-L Model')} onSelectFile={() => void selectFile('sdclipl', 'Select CLIP-L Model')}
searchParams={{ searchParams={{
search: 'clip', search: 'clip',
filter: 'safetensors', filter: 'safetensors',
@ -114,7 +114,7 @@ export const ImageGenerationTab = () => {
placeholder="Select a Clip-G file or enter a direct URL" placeholder="Select a Clip-G file or enter a direct URL"
tooltip="CLIP-G text encoder model for enhanced text-image understanding, or mmproj files for vision-language models." tooltip="CLIP-G text encoder model for enhanced text-image understanding, or mmproj files for vision-language models."
onChange={setSdclipg} onChange={setSdclipg}
onSelectFile={() => selectFile('sdclipg', 'Select CLIP-G Model')} onSelectFile={() => void selectFile('sdclipg', 'Select CLIP-G Model')}
searchParams={{ searchParams={{
search: 'clip', search: 'clip',
filter: 'gguf', filter: 'gguf',
@ -130,7 +130,7 @@ export const ImageGenerationTab = () => {
tooltip="PhotoMaker is a model that allows face cloning. Select a .safetensors PhotoMaker file to be loaded (SDXL only)." tooltip="PhotoMaker is a model that allows face cloning. Select a .safetensors PhotoMaker file to be loaded (SDXL only)."
onChange={setSdphotomaker} onChange={setSdphotomaker}
onSelectFile={() => onSelectFile={() =>
selectFile('sdphotomaker', 'Select PhotoMaker Model') void selectFile('sdphotomaker', 'Select PhotoMaker Model')
} }
searchParams={{ searchParams={{
search: 'photomaker', search: 'photomaker',
@ -146,7 +146,7 @@ export const ImageGenerationTab = () => {
placeholder="Select a VAE file or enter a direct URL" placeholder="Select a VAE file or enter a direct URL"
tooltip="Variational Autoencoder model for improved image quality." tooltip="Variational Autoencoder model for improved image quality."
onChange={setSdvae} onChange={setSdvae}
onSelectFile={() => selectFile('sdvae', 'Select VAE Model')} onSelectFile={() => void selectFile('sdvae', 'Select VAE Model')}
searchParams={{ searchParams={{
search: 'vae', search: 'vae',
filter: 'safetensors', filter: 'safetensors',
@ -161,7 +161,7 @@ export const ImageGenerationTab = () => {
placeholder="Select a LoRa file or enter a direct URL" placeholder="Select a LoRa file or enter a direct URL"
tooltip="LoRa (Low-Rank Adaptation) file for customizing image generation. Select a .safetensors or .gguf LoRa file to be loaded. Should be unquantized." tooltip="LoRa (Low-Rank Adaptation) file for customizing image generation. Select a .safetensors or .gguf LoRa file to be loaded. Should be unquantized."
onChange={setSdlora} onChange={setSdlora}
onSelectFile={() => selectFile('sdlora', 'Select LoRA Model')} onSelectFile={() => void selectFile('sdlora', 'Select LoRA Model')}
searchParams={{ searchParams={{
search: 'lora', search: 'lora',
filter: 'safetensors', filter: 'safetensors',

View file

@ -56,7 +56,7 @@ export const ModelFileField = ({
const combobox = useCombobox(); const combobox = useCombobox();
useEffect(() => { useEffect(() => {
(async () => { void (async () => {
try { try {
const models = const models =
await window.electronAPI.kobold.getLocalModels(paramType); await window.electronAPI.kobold.getLocalModels(paramType);
@ -168,7 +168,7 @@ export const ModelFileField = ({
} }
> >
<ActionIcon <ActionIcon
onClick={handleAnalyzeModel} onClick={() => void handleAnalyzeModel()}
variant="light" variant="light"
color="blue" color="blue"
size="lg" size="lg"

View file

@ -285,7 +285,7 @@ export const LaunchScreen = ({ onLaunch }: LaunchScreenProps) => {
}, [loadConfigFiles]); }, [loadConfigFiles]);
const handleLaunchClick = useCallback(() => { const handleLaunchClick = useCallback(() => {
handleLaunch({ void handleLaunch({
autoGpuLayers, autoGpuLayers,
gpuLayers, gpuLayers,
contextSize, contextSize,

View file

@ -31,7 +31,7 @@ export const AboutTab = () => {
} }
}; };
loadVersionInfo(); void loadVersionInfo();
}, []); }, []);
if (!versionInfo) { if (!versionInfo) {
@ -60,7 +60,7 @@ export const AboutTab = () => {
alt={PRODUCT_NAME} alt={PRODUCT_NAME}
w={64} w={64}
h={64} h={64}
onClick={handleLogoClick} onClick={() => void handleLogoClick()}
style={{ style={{
minWidth: 64, minWidth: 64,
minHeight: 64, minHeight: 64,
@ -93,7 +93,7 @@ export const AboutTab = () => {
leftSection={ leftSection={
<button.icon style={{ width: rem(16), height: rem(16) }} /> <button.icon style={{ width: rem(16), height: rem(16) }} />
} }
onClick={button.onClick} onClick={() => void button.onClick()}
style={ style={
button.label === 'GitHub' button.label === 'GitHub'
? { textDecoration: 'none' } ? { textDecoration: 'none' }

View file

@ -68,7 +68,7 @@ export const BackendsTab = () => {
} }
}; };
init(); void init();
}, [getLatestReleaseWithDownloadStatus, refreshDownloads]); }, [getLatestReleaseWithDownloadStatus, refreshDownloads]);
const loadInstalledBackends = useCallback(async () => { const loadInstalledBackends = useCallback(async () => {
@ -230,7 +230,7 @@ export const BackendsTab = () => {
setCurrentBackend(targetBackend); setCurrentBackend(targetBackend);
} }
window.electronAPI.kobold.setCurrentBackend(backend.installedPath); void window.electronAPI.kobold.setCurrentBackend(backend.installedPath);
}; };
if (loadingInstalled) { if (loadingInstalled) {
@ -301,19 +301,19 @@ export const BackendsTab = () => {
disabled={isDisabled} disabled={isDisabled}
onDownload={(e) => { onDownload={(e) => {
e.stopPropagation(); e.stopPropagation();
handleDownload(backend); void handleDownload(backend);
}} }}
onUpdate={(e) => { onUpdate={(e) => {
e.stopPropagation(); e.stopPropagation();
handleUpdate(backend); void handleUpdate(backend);
}} }}
onRedownload={(e) => { onRedownload={(e) => {
e.stopPropagation(); e.stopPropagation();
handleRedownload(backend); void handleRedownload(backend);
}} }}
onDelete={(e) => { onDelete={(e) => {
e.stopPropagation(); e.stopPropagation();
handleDelete(backend); void handleDelete(backend);
}} }}
onMakeCurrent={() => makeCurrent(backend)} onMakeCurrent={() => makeCurrent(backend)}
/> />

View file

@ -212,10 +212,10 @@ export const FrontendInterfaceSelector = ({
const initialize = async () => { const initialize = async () => {
await checkAllFrontendRequirements(); await checkAllFrontendRequirements();
}; };
initialize(); void initialize();
const handleFocus = () => { const handleFocus = () => {
checkAllFrontendRequirements(); void checkAllFrontendRequirements();
}; };
window.addEventListener('focus', handleFocus); window.addEventListener('focus', handleFocus);
@ -225,7 +225,7 @@ export const FrontendInterfaceSelector = ({
useEffect(() => { useEffect(() => {
if (frontendPreference) { if (frontendPreference) {
// eslint-disable-next-line react-hooks/set-state-in-effect // eslint-disable-next-line react-hooks/set-state-in-effect
checkAllFrontendRequirements(); void checkAllFrontendRequirements();
} }
}, [frontendPreference, checkAllFrontendRequirements]); }, [frontendPreference, checkAllFrontendRequirements]);
@ -323,7 +323,7 @@ export const FrontendInterfaceSelector = ({
> >
Cancel Cancel
</Button> </Button>
<Button color="red" onClick={handleClearOpenWebUIData}> <Button color="red" onClick={() => void handleClearOpenWebUIData()}>
Clear Data Clear Data
</Button> </Button>
</Group> </Group>

View file

@ -19,7 +19,7 @@ export const GeneralTab = () => {
setStartMinimizedToTray(startMinimized); setStartMinimizedToTray(startMinimized);
}; };
loadSystemTrayPreference(); void loadSystemTrayPreference();
}, []); }, []);
const handleSystemTrayToggle = async (checked: boolean) => { const handleSystemTrayToggle = async (checked: boolean) => {
@ -66,7 +66,7 @@ export const GeneralTab = () => {
label="Enable system tray icon" label="Enable system tray icon"
checked={enableSystemTray} checked={enableSystemTray}
onChange={(event) => onChange={(event) =>
handleSystemTrayToggle(event.currentTarget.checked) void handleSystemTrayToggle(event.currentTarget.checked)
} }
/> />
</div> </div>
@ -83,7 +83,7 @@ export const GeneralTab = () => {
checked={startMinimizedToTray} checked={startMinimizedToTray}
disabled={!enableSystemTray} disabled={!enableSystemTray}
onChange={(event) => onChange={(event) =>
handleStartMinimizedToggle(event.currentTarget.checked) void handleStartMinimizedToggle(event.currentTarget.checked)
} }
/> />
</div> </div>

View file

@ -56,7 +56,7 @@ export const SystemTab = () => {
} }
}; };
loadAll(); void loadAll();
}, []); }, []);
if (loading || !versionInfo) { if (loading || !versionInfo) {

View file

@ -13,7 +13,7 @@ export const TroubleshootingTab = () => {
} }
}; };
loadCurrentInstallDir(); void loadCurrentInstallDir();
}, []); }, []);
const handleSelectInstallDir = async () => { const handleSelectInstallDir = async () => {
@ -49,7 +49,7 @@ export const TroubleshootingTab = () => {
/> />
<Button <Button
variant="outline" variant="outline"
onClick={handleSelectInstallDir} onClick={() => void handleSelectInstallDir()}
leftSection={ leftSection={
<FolderOpen style={{ width: rem(16), height: rem(16) }} /> <FolderOpen style={{ width: rem(16), height: rem(16) }} />
} }
@ -58,7 +58,7 @@ export const TroubleshootingTab = () => {
</Button> </Button>
<Button <Button
variant="outline" variant="outline"
onClick={handleOpenInstallDir} onClick={() => void handleOpenInstallDir()}
disabled={!installDir} disabled={!installDir}
leftSection={ leftSection={
<ExternalLink style={{ width: rem(16), height: rem(16) }} /> <ExternalLink style={{ width: rem(16), height: rem(16) }} />
@ -83,7 +83,7 @@ export const TroubleshootingTab = () => {
leftSection={ leftSection={
<FolderOpen style={{ width: rem(16), height: rem(16) }} /> <FolderOpen style={{ width: rem(16), height: rem(16) }} />
} }
onClick={() => window.electronAPI.app.showLogsFolder()} onClick={() => void window.electronAPI.app.showLogsFolder()}
> >
Show Logs Show Logs
</Button> </Button>
@ -93,7 +93,7 @@ export const TroubleshootingTab = () => {
leftSection={ leftSection={
<Monitor style={{ width: rem(16), height: rem(16) }} /> <Monitor style={{ width: rem(16), height: rem(16) }} />
} }
onClick={() => window.electronAPI.app.viewConfigFile()} onClick={() => void window.electronAPI.app.viewConfigFile()}
> >
View Config View Config
</Button> </Button>

View file

@ -50,7 +50,7 @@ export const useAppUpdateChecker = () => {
const installUpdate = useCallback(() => { const installUpdate = useCallback(() => {
if (isUpdateDownloaded) { if (isUpdateDownloaded) {
window.electronAPI.updater.quitAndInstall(); void window.electronAPI.updater.quitAndInstall();
} }
}, [isUpdateDownloaded]); }, [isUpdateDownloaded]);

View file

@ -184,7 +184,7 @@ export const useHuggingFaceSearch = (
const changeSortOrder = useCallback( const changeSortOrder = useCallback(
(sort: HuggingFaceSortOption, query?: string) => { (sort: HuggingFaceSortOption, query?: string) => {
searchModels(query, true, sort); void searchModels(query, true, sort);
}, },
[searchModels] [searchModels]
); );

View file

@ -35,7 +35,7 @@ export const useUpdateChecker = () => {
} }
}; };
loadDismissedUpdates(); void loadDismissedUpdates();
}, []); }, []);
const checkForUpdates = useCallback(async () => { const checkForUpdates = useCallback(async () => {

View file

@ -235,7 +235,7 @@ export const useWarnings = ({
useEffect(() => { useEffect(() => {
// eslint-disable-next-line react-hooks/set-state-in-effect // eslint-disable-next-line react-hooks/set-state-in-effect
updateBackendWarnings(); void updateBackendWarnings();
}, [updateBackendWarnings]); }, [updateBackendWarnings]);
const allWarnings = useMemo( const allWarnings = useMemo(

View file

@ -86,10 +86,10 @@ export async function handleCliMode(args: string[]) {
} }
}; };
on('SIGINT', handleSignal); on('SIGINT', () => void handleSignal());
on('SIGTERM', handleSignal); on('SIGTERM', () => void handleSignal());
if (platform === 'win32') { if (platform === 'win32') {
on('SIGBREAK', handleSignal); on('SIGBREAK', () => void handleSignal());
} }
}); });
} }

View file

@ -67,12 +67,12 @@ export async function initializeApp(options?: { startMinimized?: boolean }) {
app.quit(); app.quit();
}); });
app.on('before-quit', async (event) => { app.on('before-quit', (event) => {
event.preventDefault(); event.preventDefault();
await safeExecute(async () => { void safeExecute(async () => {
const cleanupPromises = [ const cleanupPromises = [
cleanupWindow(), Promise.resolve(cleanupWindow()),
stopKoboldCpp(), stopKoboldCpp(),
stopSillyTavern(), stopSillyTavern(),
stopOpenWebUI(), stopOpenWebUI(),

View file

@ -1,10 +1,10 @@
import { argv, exit } from 'process'; import { argv, exit } from 'process';
if (argv[1] === '--version') { if (argv[1] === '--version') {
(async () => { void (async () => {
try { try {
const { app } = await import('electron'); const { app } = await import('electron');
const version = await app.getVersion(); const version = app.getVersion();
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(version); console.log(version);
} catch { } catch {
@ -14,7 +14,7 @@ if (argv[1] === '--version') {
exit(0); exit(0);
})(); })();
} else { } else {
(async () => { void (async () => {
const isCliMode = argv.includes('--cli'); const isCliMode = argv.includes('--cli');
if (isCliMode) { if (isCliMode) {

View file

@ -150,9 +150,9 @@ export function setupIPCHandlers() {
); );
ipcMain.handle('kobold:stopKoboldCpp', () => { ipcMain.handle('kobold:stopKoboldCpp', () => {
stopKoboldCpp(); void stopKoboldCpp();
stopSillyTavernFrontend(); void stopSillyTavernFrontend();
stopOpenWebUIFrontend(); void stopOpenWebUIFrontend();
}); });
ipcMain.handle('kobold:parseConfigFile', (_, filePath) => ipcMain.handle('kobold:parseConfigFile', (_, filePath) =>
@ -196,13 +196,13 @@ export function setupIPCHandlers() {
ipcMain.handle('config:get', (_, key) => getConfig(key)); ipcMain.handle('config:get', (_, key) => getConfig(key));
ipcMain.on('config:set', (_, key, value) => setConfig(key, value)); ipcMain.on('config:set', (_, key, value) => void setConfig(key, value));
ipcMain.handle('app:getVersion', () => app.getVersion()); ipcMain.handle('app:getVersion', () => app.getVersion());
ipcMain.handle('app:getVersionInfo', () => getVersionInfo()); ipcMain.handle('app:getVersionInfo', () => getVersionInfo());
ipcMain.handle('app:openPath', (_, path) => openPathHandler(path)); ipcMain.handle('app:openPath', async (_, path) => openPathHandler(path));
ipcMain.handle('app:showLogsFolder', () => ipcMain.handle('app:showLogsFolder', () =>
openPathHandler(join(app.getPath('userData'), 'logs')) openPathHandler(join(app.getPath('userData'), 'logs'))
@ -230,12 +230,12 @@ export function setupIPCHandlers() {
ipcMain.handle('app:setZoomLevel', (_, level) => { ipcMain.handle('app:setZoomLevel', (_, level) => {
mainWindow.webContents.setZoomLevel(level); mainWindow.webContents.setZoomLevel(level);
setConfig('zoomLevel', level); void setConfig('zoomLevel', level);
}); });
ipcMain.handle('app:getColorScheme', () => getColorScheme()); ipcMain.handle('app:getColorScheme', () => getColorScheme());
ipcMain.handle('app:setColorScheme', (_, colorScheme) => ipcMain.handle('app:setColorScheme', async (_, colorScheme) =>
setConfig('colorScheme', colorScheme) setConfig('colorScheme', colorScheme)
); );
@ -275,8 +275,8 @@ export function setupIPCHandlers() {
ipcMain.handle('app:openExternal', async (_, url) => openUrl(url)); ipcMain.handle('app:openExternal', async (_, url) => openUrl(url));
mainWindow.webContents.once('did-finish-load', async () => { mainWindow.webContents.once('did-finish-load', () => {
const savedZoomLevel = await getConfig('zoomLevel'); const savedZoomLevel = getConfig('zoomLevel');
if (typeof savedZoomLevel === 'number') { if (typeof savedZoomLevel === 'number') {
mainWindow.webContents.setZoomLevel(savedZoomLevel); mainWindow.webContents.setZoomLevel(savedZoomLevel);
} }

View file

@ -84,7 +84,10 @@ async function downloadFile(asset: GitHubAsset, tempPackedFilePath: string) {
await pump(); await pump();
await new Promise<void>((resolve, reject) => { await new Promise<void>((resolve, reject) => {
writer.on('finish', async () => { writer.on(
'finish',
() =>
void (async () => {
if (platform !== 'win32') { if (platform !== 'win32') {
try { try {
await chmod(tempPackedFilePath, 0o755); await chmod(tempPackedFilePath, 0o755);
@ -93,7 +96,8 @@ async function downloadFile(asset: GitHubAsset, tempPackedFilePath: string) {
} }
} }
resolve(); resolve();
}); })()
);
writer.on('error', reject); writer.on('error', reject);
}); });
} }

View file

@ -222,7 +222,7 @@ export async function launchKoboldCpp(
sendKoboldOutput(commandLine); sendKoboldOutput(commandLine);
if (remotetunnel) { if (remotetunnel) {
startTunnel(frontendPreference); void startTunnel(frontendPreference);
} }
let readyResolve: let readyResolve:
@ -334,9 +334,9 @@ export async function launchKoboldCpp(
export async function stopKoboldCpp() { export async function stopKoboldCpp() {
abortActiveDownloads(); abortActiveDownloads();
stopProxy(); void stopProxy();
stopTunnel(); void stopTunnel();
stopPreLaunchProcesses(); void stopPreLaunchProcesses();
isIntentionalStop = true; isIntentionalStop = true;
return terminateProcess(koboldProcess); return terminateProcess(koboldProcess);
} }
@ -346,14 +346,10 @@ export const launchKoboldCppWithCustomFrontends = async (
preLaunchCommands: string[] = [] preLaunchCommands: string[] = []
) => ) =>
safeExecute(async () => { safeExecute(async () => {
const [frontendPreference, imageGenerationFrontendPreference] = const frontendPreference = getConfig('frontendPreference');
(await Promise.all([ const imageGenerationFrontendPreference = getConfig(
getConfig('frontendPreference'), 'imageGenerationFrontendPreference'
getConfig('imageGenerationFrontendPreference'), );
])) as [
FrontendPreference,
ImageGenerationFrontendPreference | undefined,
];
const { isTextMode } = parseKoboldConfig(args); const { isTextMode } = parseKoboldConfig(args);
@ -372,9 +368,9 @@ export const launchKoboldCppWithCustomFrontends = async (
} }
if (frontendPreference === 'sillytavern') { if (frontendPreference === 'sillytavern') {
startSillyTavernFrontend(args); void startSillyTavernFrontend(args);
} else if (frontendPreference === 'openwebui') { } else if (frontendPreference === 'openwebui') {
startOpenWebUIFrontend(args); void startOpenWebUIFrontend(args);
} }
return result; return result;

View file

@ -94,7 +94,7 @@ async function downloadFile(
abort: () => { abort: () => {
isAborted = true; isAborted = true;
currentRequest?.destroy(); currentRequest?.destroy();
cleanup(); void cleanup();
reject(new Error('Download aborted by user')); reject(new Error('Download aborted by user'));
}, },
tempPath, tempPath,
@ -197,7 +197,7 @@ async function downloadFile(
if (totalBytes > 0 && downloadedBytes !== totalBytes) { if (totalBytes > 0 && downloadedBytes !== totalBytes) {
activeDownloads.delete(abortController); activeDownloads.delete(abortController);
cleanup(); void cleanup();
reject( reject(
new Error( new Error(
`Incomplete download: received ${downloadedBytes} bytes, expected ${totalBytes} bytes` `Incomplete download: received ${downloadedBytes} bytes, expected ${totalBytes} bytes`
@ -207,7 +207,10 @@ async function downloadFile(
} }
fileStream.end(); fileStream.end();
fileStream.on('finish', async () => { fileStream.on(
'finish',
() =>
void (async () => {
try { try {
await rename(tempPath, outputPath); await rename(tempPath, outputPath);
sendKoboldOutput('\n'); sendKoboldOutput('\n');
@ -217,22 +220,31 @@ async function downloadFile(
activeDownloads.delete(abortController); activeDownloads.delete(abortController);
reject(err instanceof Error ? err : new Error(String(err))); reject(err instanceof Error ? err : new Error(String(err)));
} }
}); })()
);
}); });
response.on('error', async (err) => { response.on(
'error',
(err) =>
void (async () => {
if (isAborted) return; if (isAborted) return;
activeDownloads.delete(abortController); activeDownloads.delete(abortController);
await cleanup(); await cleanup();
reject(err); reject(err);
}); })()
);
fileStream.on('error', async (err) => { fileStream.on(
'error',
(err) =>
void (async () => {
if (isAborted) return; if (isAborted) return;
activeDownloads.delete(abortController); activeDownloads.delete(abortController);
await cleanup(); await cleanup();
reject(err); reject(err);
}); })()
);
}).on('error', (err) => { }).on('error', (err) => {
if (isAborted) return; if (isAborted) return;
activeDownloads.delete(abortController); activeDownloads.delete(abortController);

View file

@ -64,20 +64,20 @@ export function startMonitoring(window: BrowserWindow) {
mainWindow = window; mainWindow = window;
isRunning = true; isRunning = true;
collectAndSendCpuMetrics(); void collectAndSendCpuMetrics();
cpuInterval = setInterval(() => { cpuInterval = setInterval(() => {
collectAndSendCpuMetrics(); void collectAndSendCpuMetrics();
}, updateFrequency); }, updateFrequency);
collectAndSendMemoryMetrics(); void collectAndSendMemoryMetrics();
memoryInterval = setInterval(() => { memoryInterval = setInterval(() => {
collectAndSendMemoryMetrics(); void collectAndSendMemoryMetrics();
}, updateFrequency); }, updateFrequency);
if (platform === 'linux') { if (platform === 'linux') {
collectAndSendGpuMetrics(); void collectAndSendGpuMetrics();
gpuInterval = setInterval(() => { gpuInterval = setInterval(() => {
collectAndSendGpuMetrics(); void collectAndSendGpuMetrics();
}, updateFrequency); }, updateFrequency);
} }
} }

View file

@ -82,10 +82,8 @@ export async function startFrontend(args: string[]) {
isImageMode, isImageMode,
} = parseKoboldConfig(args); } = parseKoboldConfig(args);
const [, appVersion] = await Promise.all([ await stopFrontend();
stopFrontend(), const appVersion = app.getVersion();
app.getVersion(),
]);
sendKoboldOutput( sendKoboldOutput(
`Preparing Open WebUI to connect at ${koboldHost}:${koboldPort}${isImageMode ? ' (with image generation)' : ''}...` `Preparing Open WebUI to connect at ${koboldHost}:${koboldPort}${isImageMode ? ' (with image generation)' : ''}...`

View file

@ -187,17 +187,23 @@ async function ensureSillyTavernSettings() {
const output = data.toString(); const output = data.toString();
if (output.includes(SERVER_READY_SIGNALS.SILLYTAVERN)) { if (output.includes(SERVER_READY_SIGNALS.SILLYTAVERN)) {
setTimeout(async () => { setTimeout(
() =>
void (async () => {
if (!initProcess.killed && !hasResolved) { if (!initProcess.killed && !hasResolved) {
hasResolved = true; hasResolved = true;
await terminateProcess(initProcess); await terminateProcess(initProcess);
sendKoboldOutput('SillyTavern settings should now be generated'); sendKoboldOutput(
'SillyTavern settings should now be generated'
);
resolve(); resolve();
} }
}, 2000); })(),
2000
);
} }
}); });
} }

View file

@ -9,7 +9,9 @@ let serverPort = 0;
export const startStaticServer = (distPath: string) => export const startStaticServer = (distPath: string) =>
new Promise<string>((resolve, reject) => { new Promise<string>((resolve, reject) => {
server = createServer(async (req, res) => { server = createServer(
(req, res) =>
void (async () => {
const requestPath = req.url === '/' ? 'index.html' : req.url!; const requestPath = req.url === '/' ? 'index.html' : req.url!;
let filePath = join(distPath, requestPath); let filePath = join(distPath, requestPath);
@ -27,7 +29,8 @@ export const startStaticServer = (distPath: string) =>
res.writeHead(404); res.writeHead(404);
res.end('Not found'); res.end('Not found');
} }
}); })()
);
server.listen(0, 'localhost', () => { server.listen(0, 'localhost', () => {
const address = server!.address(); const address = server!.address();

View file

@ -171,6 +171,7 @@ function setupContextMenu(mainWindow: BrowserWindow) {
const hasSelection = !!params.selectionText; const hasSelection = !!params.selectionText;
const isEditable = params.isEditable; const isEditable = params.isEditable;
const isDev = isDevelopment; const isDev = isDevelopment;
const hasMisspelling = !!params.misspelledWord;
const canCut = hasSelection && isEditable; const canCut = hasSelection && isEditable;
const canCopy = hasSelection; const canCopy = hasSelection;
@ -192,11 +193,40 @@ function setupContextMenu(mainWindow: BrowserWindow) {
}); });
} }
if (hasEditOperations) { if (hasMisspelling && params.dictionarySuggestions.length > 0) {
if (isDev) { if (isDev) {
menuItems.push({ type: 'separator' as const }); menuItems.push({ type: 'separator' as const });
} }
params.dictionarySuggestions.forEach((suggestion) => {
menuItems.push({
label: suggestion,
click: () => {
mainWindow.webContents.replaceMisspelling(suggestion);
},
});
});
menuItems.push({ type: 'separator' as const });
menuItems.push({
label: 'Add to Dictionary',
click: () => {
mainWindow.webContents.session.addWordToSpellCheckerDictionary(
params.misspelledWord
);
},
});
if (hasEditOperations) {
menuItems.push({ type: 'separator' as const });
}
}
if (hasEditOperations) {
if (isDev && !hasMisspelling) {
menuItems.push({ type: 'separator' as const });
}
if (canUndo) { if (canUndo) {
menuItems.push({ label: 'Undo', role: 'undo' as const }); menuItems.push({ label: 'Undo', role: 'undo' as const });
} }

View file

@ -74,7 +74,7 @@ const koboldAPI: KoboldAPI = {
flashAttention, flashAttention,
acceleration acceleration
), ),
stopKoboldCpp: () => ipcRenderer.invoke('kobold:stopKoboldCpp'), stopKoboldCpp: () => void ipcRenderer.invoke('kobold:stopKoboldCpp'),
onDownloadProgress: (callback) => { onDownloadProgress: (callback) => {
const handler = (_: IpcRendererEvent, progress: number) => const handler = (_: IpcRendererEvent, progress: number) =>
callback(progress); callback(progress);
@ -141,14 +141,14 @@ const appAPI: AppAPI = {
openPath: (path) => ipcRenderer.invoke('app:openPath', path), openPath: (path) => ipcRenderer.invoke('app:openPath', path),
getVersion: () => ipcRenderer.invoke('app:getVersion'), getVersion: () => ipcRenderer.invoke('app:getVersion'),
getVersionInfo: () => ipcRenderer.invoke('app:getVersionInfo'), getVersionInfo: () => ipcRenderer.invoke('app:getVersionInfo'),
minimizeWindow: () => ipcRenderer.invoke('app:minimizeWindow'), minimizeWindow: async () => ipcRenderer.invoke('app:minimizeWindow'),
maximizeWindow: () => ipcRenderer.invoke('app:maximizeWindow'), maximizeWindow: async () => ipcRenderer.invoke('app:maximizeWindow'),
closeWindow: () => ipcRenderer.invoke('app:closeWindow'), closeWindow: async () => ipcRenderer.invoke('app:closeWindow'),
isMaximized: () => ipcRenderer.invoke('app:isMaximized'), isMaximized: () => ipcRenderer.invoke('app:isMaximized'),
getZoomLevel: () => ipcRenderer.invoke('app:getZoomLevel'), getZoomLevel: () => ipcRenderer.invoke('app:getZoomLevel'),
setZoomLevel: (level) => ipcRenderer.invoke('app:setZoomLevel', level), setZoomLevel: async (level) => ipcRenderer.invoke('app:setZoomLevel', level),
getColorScheme: () => ipcRenderer.invoke('app:getColorScheme'), getColorScheme: () => ipcRenderer.invoke('app:getColorScheme'),
setColorScheme: (colorScheme) => setColorScheme: async (colorScheme) =>
ipcRenderer.invoke('app:setColorScheme', colorScheme), ipcRenderer.invoke('app:setColorScheme', colorScheme),
getEnableSystemTray: () => ipcRenderer.invoke('app:getEnableSystemTray'), getEnableSystemTray: () => ipcRenderer.invoke('app:getEnableSystemTray'),
setEnableSystemTray: (enabled) => setEnableSystemTray: (enabled) =>
@ -252,7 +252,7 @@ const monitoringAPI: MonitoringAPI = {
const updaterAPI: UpdaterAPI = { const updaterAPI: UpdaterAPI = {
checkForUpdates: () => ipcRenderer.invoke('app:checkForUpdates'), checkForUpdates: () => ipcRenderer.invoke('app:checkForUpdates'),
downloadUpdate: () => ipcRenderer.invoke('app:downloadUpdate'), downloadUpdate: () => ipcRenderer.invoke('app:downloadUpdate'),
quitAndInstall: () => ipcRenderer.invoke('app:quitAndInstall'), quitAndInstall: async () => ipcRenderer.invoke('app:quitAndInstall'),
isUpdateDownloaded: () => ipcRenderer.invoke('app:isUpdateDownloaded'), isUpdateDownloaded: () => ipcRenderer.invoke('app:isUpdateDownloaded'),
canAutoUpdate: () => ipcRenderer.invoke('app:canAutoUpdate'), canAutoUpdate: () => ipcRenderer.invoke('app:canAutoUpdate'),
isAURInstallation: () => ipcRenderer.invoke('app:isAURInstallation'), isAURInstallation: () => ipcRenderer.invoke('app:isAURInstallation'),

View file

@ -203,4 +203,4 @@ export const useKoboldBackendsStore = create<KoboldBackendsState>(
}) })
); );
useKoboldBackendsStore.getState().initialize(); void useKoboldBackendsStore.getState().initialize();

View file

@ -71,7 +71,10 @@ export const useNotepadStore = create<NotepadStore>()(
t.title === title ? { ...t, content: DEFAULT_TAB_CONTENT } : t t.title === title ? { ...t, content: DEFAULT_TAB_CONTENT } : t
), ),
})); }));
window.electronAPI.notepad.saveTabContent(title, DEFAULT_TAB_CONTENT); void window.electronAPI.notepad.saveTabContent(
title,
DEFAULT_TAB_CONTENT
);
} }
return; return;
@ -90,7 +93,7 @@ export const useNotepadStore = create<NotepadStore>()(
activeTabId: newActiveTabId, activeTabId: newActiveTabId,
}); });
window.electronAPI.notepad.deleteTab(title); void window.electronAPI.notepad.deleteTab(title);
}, },
reorderTabs: (fromIndex, toIndex) => { reorderTabs: (fromIndex, toIndex) => {
@ -184,7 +187,7 @@ useNotepadStore.subscribe(
}), }),
() => { () => {
if (useNotepadStore.getState().isLoaded) { if (useNotepadStore.getState().isLoaded) {
useNotepadStore.getState().saveState(); void useNotepadStore.getState().saveState();
} }
}, },
{ {
@ -198,5 +201,5 @@ useNotepadStore.subscribe(
); );
setTimeout(() => { setTimeout(() => {
useNotepadStore.getState().loadState(); void useNotepadStore.getState().loadState();
}, 0); }, 0);

View file

@ -208,9 +208,9 @@ export interface AppAPI {
openPath: (path: string) => Promise<void>; openPath: (path: string) => Promise<void>;
getVersion: () => Promise<string>; getVersion: () => Promise<string>;
getVersionInfo: () => Promise<SystemVersionInfo>; getVersionInfo: () => Promise<SystemVersionInfo>;
minimizeWindow: () => void; minimizeWindow: () => Promise<void>;
maximizeWindow: () => void; maximizeWindow: () => Promise<void>;
closeWindow: () => void; closeWindow: () => Promise<void>;
isMaximized: () => Promise<boolean>; isMaximized: () => Promise<boolean>;
getZoomLevel: () => Promise<number>; getZoomLevel: () => Promise<number>;
setZoomLevel: (level: number) => Promise<void>; setZoomLevel: (level: number) => Promise<void>;
@ -267,7 +267,7 @@ export interface MonitoringAPI {
export interface UpdaterAPI { export interface UpdaterAPI {
checkForUpdates: () => Promise<boolean>; checkForUpdates: () => Promise<boolean>;
downloadUpdate: () => Promise<boolean>; downloadUpdate: () => Promise<boolean>;
quitAndInstall: () => void; quitAndInstall: () => Promise<void>;
isUpdateDownloaded: () => Promise<boolean>; isUpdateDownloaded: () => Promise<boolean>;
canAutoUpdate: () => Promise<boolean>; canAutoUpdate: () => Promise<boolean>;
isAURInstallation: () => Promise<boolean>; isAURInstallation: () => Promise<boolean>;

View file

@ -24,7 +24,13 @@ const createAppLogger = () => {
format.timestamp({ format.timestamp({
format: 'YYYY-MM-DD HH:mm:ss.SSS', format: 'YYYY-MM-DD HH:mm:ss.SSS',
}), }),
format.printf(({ timestamp, level, message, error }) => { format.printf((info) => {
const { timestamp, level, message, error } = info as {
timestamp: string;
level: string;
message: string;
error?: Error;
};
let logEntry = `${timestamp} [MAIN] [${level.toUpperCase()}] ${message}`; let logEntry = `${timestamp} [MAIN] [${level.toUpperCase()}] ${message}`;
if (error && error instanceof Error) { if (error && error instanceof Error) {

View file

@ -50,11 +50,11 @@ export const openPathHandler = async (path: string) =>
error: 'Failed to open path', error: 'Failed to open path',
}; };
export const openUrl = (url: string) => export const openUrl = async (url: string) =>
safeExecute(async () => { (await safeExecute(async () => {
await shell.openExternal(url); await shell.openExternal(url);
return { success: true }; return { success: true };
}, 'Failed to open external URL') || { }, 'Failed to open external URL')) || {
success: false, success: false,
error: 'Failed to open external URL', error: 'Failed to open external URL',
}; };