mirror of
https://github.com/lone-cloud/gerbil
synced 2026-06-03 19:54:44 -07:00
better app colors, more borders for easier window separation, allow for beta releases, app auto-updates
This commit is contained in:
parent
ae99ac77ae
commit
6d1d73fa5b
22 changed files with 464 additions and 177 deletions
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
|
@ -37,7 +37,7 @@ jobs:
|
||||||
run: yarn
|
run: yarn
|
||||||
|
|
||||||
- name: Run checks (lint, type check, spell check)
|
- name: Run checks (lint, type check, spell check)
|
||||||
run: yarn check-all
|
run: yarn test
|
||||||
|
|
||||||
- name: Test build
|
- name: Test build
|
||||||
run: yarn build
|
run: yarn build
|
||||||
|
|
|
||||||
35
.github/workflows/release.yml
vendored
35
.github/workflows/release.yml
vendored
|
|
@ -90,6 +90,8 @@ jobs:
|
||||||
needs: build
|
needs: build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch'
|
if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch'
|
||||||
|
outputs:
|
||||||
|
is_prerelease: ${{ steps.release_type.outputs.is_prerelease }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
|
|
@ -112,12 +114,29 @@ jobs:
|
||||||
echo "tag=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
|
echo "tag=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
- name: Determine release type
|
||||||
|
id: release_type
|
||||||
|
run: |
|
||||||
|
if [[ "${{ steps.tag.outputs.tag }}" =~ -alpha|-beta|-rc ]]; then
|
||||||
|
echo "is_prerelease=true" >> $GITHUB_OUTPUT
|
||||||
|
echo "📋 This will be marked as a pre-release"
|
||||||
|
else
|
||||||
|
echo "is_prerelease=false" >> $GITHUB_OUTPUT
|
||||||
|
echo "📋 This will be marked as a full release"
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Create Release
|
- name: Create Release
|
||||||
run: |
|
run: |
|
||||||
gh release create ${{ steps.tag.outputs.tag }} \
|
if [ "${{ steps.release_type.outputs.is_prerelease }}" = "true" ]; then
|
||||||
--title "Gerbil ${{ steps.tag.outputs.tag }}" \
|
gh release create ${{ steps.tag.outputs.tag }} \
|
||||||
--generate-notes \
|
--title "Gerbil ${{ steps.tag.outputs.tag }}" \
|
||||||
--prerelease
|
--generate-notes \
|
||||||
|
--prerelease
|
||||||
|
else
|
||||||
|
gh release create ${{ steps.tag.outputs.tag }} \
|
||||||
|
--title "Gerbil ${{ steps.tag.outputs.tag }}" \
|
||||||
|
--generate-notes
|
||||||
|
fi
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
|
@ -129,6 +148,9 @@ jobs:
|
||||||
gh release upload ${{ steps.tag.outputs.tag }} "$file" --clobber
|
gh release upload ${{ steps.tag.outputs.tag }} "$file" --clobber
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
if [ -f "artifacts/macos-release/latest-mac.yml" ]; then
|
||||||
|
gh release upload ${{ steps.tag.outputs.tag }} "artifacts/macos-release/latest-mac.yml" --clobber
|
||||||
|
fi
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
|
@ -140,6 +162,9 @@ jobs:
|
||||||
gh release upload ${{ steps.tag.outputs.tag }} "$file" --clobber
|
gh release upload ${{ steps.tag.outputs.tag }} "$file" --clobber
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
if [ -f "artifacts/windows-release/latest.yml" ]; then
|
||||||
|
gh release upload ${{ steps.tag.outputs.tag }} "artifacts/windows-release/latest.yml" --clobber
|
||||||
|
fi
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
|
@ -156,6 +181,8 @@ jobs:
|
||||||
|
|
||||||
aur-release:
|
aur-release:
|
||||||
needs: release
|
needs: release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: needs.release.outputs.is_prerelease != 'true'
|
||||||
uses: ./.github/workflows/aur-release.yml
|
uses: ./.github/workflows/aur-release.yml
|
||||||
with:
|
with:
|
||||||
tag: ${{ github.ref_name }}
|
tag: ${{ github.ref_name }}
|
||||||
|
|
|
||||||
2
.vscode/tasks.json
vendored
2
.vscode/tasks.json
vendored
|
|
@ -48,7 +48,7 @@
|
||||||
{
|
{
|
||||||
"label": "Check All (Lint + Type)",
|
"label": "Check All (Lint + Type)",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "yarn check-all",
|
"command": "yarn test",
|
||||||
"group": "test",
|
"group": "test",
|
||||||
"presentation": {
|
"presentation": {
|
||||||
"echo": true,
|
"echo": true,
|
||||||
|
|
|
||||||
|
|
@ -185,9 +185,7 @@ You can use the CLI mode on Windows in exactly the same way as in the Linux/macO
|
||||||
|
|
||||||
## Future Considerations
|
## Future Considerations
|
||||||
|
|
||||||
- migrate the project to Tauri from Electron?
|
|
||||||
- transition to using llama.cpp binaries directly instead of running them indirectly through koboldcpp?
|
- transition to using llama.cpp binaries directly instead of running them indirectly through koboldcpp?
|
||||||
- seamless support for more frontends?
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -135,18 +135,6 @@ const config = [
|
||||||
message:
|
message:
|
||||||
'Direct setting of currentKoboldBinary config is forbidden. Use window.electronAPI.kobold.setCurrentVersion() instead.',
|
'Direct setting of currentKoboldBinary config is forbidden. Use window.electronAPI.kobold.setCurrentVersion() instead.',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
selector:
|
|
||||||
'ExpressionStatement[expression.type="AwaitExpression"]:has(CallExpression[callee.name="ensureDir"]) + ExpressionStatement[expression.type="AwaitExpression"]:has(CallExpression[callee.name="ensureDir"])',
|
|
||||||
message:
|
|
||||||
'Sequential ensureDir() calls detected. These can run in parallel using Promise.all().',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
selector:
|
|
||||||
'ExpressionStatement[expression.type="AwaitExpression"]:has(CallExpression[callee.object.name="fs"][callee.property.name=/^(unlink|rmdir|mkdir|writeFile)$/]) + ExpressionStatement[expression.type="AwaitExpression"]:has(CallExpression[callee.object.name="fs"][callee.property.name=/^(unlink|rmdir|mkdir|writeFile)$/])',
|
|
||||||
message:
|
|
||||||
'Sequential file system operations detected. Independent operations can run in parallel using Promise.all().',
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
|
|
||||||
'import/no-default-export': 'error',
|
'import/no-default-export': 'error',
|
||||||
|
|
@ -168,6 +156,7 @@ const config = [
|
||||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||||
'@typescript-eslint/return-await': ['error', 'never'],
|
'@typescript-eslint/return-await': ['error', 'never'],
|
||||||
|
'@typescript-eslint/require-await': 'error',
|
||||||
'@typescript-eslint/prefer-promise-reject-errors': 'error',
|
'@typescript-eslint/prefer-promise-reject-errors': 'error',
|
||||||
|
|
||||||
'sonarjs/cognitive-complexity': ['warn', 25],
|
'sonarjs/cognitive-complexity': ['warn', 25],
|
||||||
|
|
|
||||||
17
package.json
17
package.json
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "gerbil",
|
"name": "gerbil",
|
||||||
"productName": "Gerbil",
|
"productName": "Gerbil",
|
||||||
"version": "1.3.4",
|
"version": "1.4.0-beta.1",
|
||||||
"description": "Run Large Language Models locally",
|
"description": "Run Large Language Models locally",
|
||||||
"main": "out/main/index.js",
|
"main": "out/main/index.js",
|
||||||
"homepage": "./",
|
"homepage": "./",
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"lint:fix": "eslint . --fix",
|
"lint:fix": "eslint . --fix",
|
||||||
"compile": "tsc --noEmit",
|
"compile": "tsc --noEmit",
|
||||||
"check-all": "yarn lint && yarn compile",
|
"test": "yarn lint && yarn compile",
|
||||||
"release": "yarn dlx tsx scripts/release.ts"
|
"release": "yarn dlx tsx scripts/release.ts"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|
@ -39,14 +39,14 @@
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.35.0",
|
"@eslint/js": "^9.35.0",
|
||||||
"@types/node": "^24.5.1",
|
"@types/node": "^24.5.2",
|
||||||
"@types/react": "^19.1.13",
|
"@types/react": "^19.1.13",
|
||||||
"@types/react-dom": "^19.1.9",
|
"@types/react-dom": "^19.1.9",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.44.0",
|
"@typescript-eslint/eslint-plugin": "^8.44.0",
|
||||||
"@typescript-eslint/parser": "^8.44.0",
|
"@typescript-eslint/parser": "^8.44.0",
|
||||||
"@vitejs/plugin-react": "^5.0.3",
|
"@vitejs/plugin-react": "^5.0.3",
|
||||||
"cross-env": "^10.0.0",
|
"cross-env": "^10.0.0",
|
||||||
"electron": "^38.1.1",
|
"electron": "^38.1.2",
|
||||||
"electron-builder": "^26.0.12",
|
"electron-builder": "^26.0.12",
|
||||||
"electron-vite": "^4.0.0",
|
"electron-vite": "^4.0.0",
|
||||||
"eslint": "^9.35.0",
|
"eslint": "^9.35.0",
|
||||||
|
|
@ -61,7 +61,7 @@
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
"rollup-plugin-visualizer": "^6.0.3",
|
"rollup-plugin-visualizer": "^6.0.3",
|
||||||
"typescript": "^5.9.2",
|
"typescript": "^5.9.2",
|
||||||
"vite": "^7.1.5"
|
"vite": "^7.1.6"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource/inter": "^5.2.8",
|
"@fontsource/inter": "^5.2.8",
|
||||||
|
|
@ -69,6 +69,7 @@
|
||||||
"@mantine/hooks": "^8.3.1",
|
"@mantine/hooks": "^8.3.1",
|
||||||
"@types/yauzl": "^2.10.3",
|
"@types/yauzl": "^2.10.3",
|
||||||
"axios": "^1.12.2",
|
"axios": "^1.12.2",
|
||||||
|
"electron-updater": "6.6.2",
|
||||||
"execa": "^9.6.0",
|
"execa": "^9.6.0",
|
||||||
"lucide-react": "^0.544.0",
|
"lucide-react": "^0.544.0",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
|
|
@ -84,7 +85,11 @@
|
||||||
"appId": "com.gerbil.app",
|
"appId": "com.gerbil.app",
|
||||||
"productName": "Gerbil",
|
"productName": "Gerbil",
|
||||||
"compression": "normal",
|
"compression": "normal",
|
||||||
"publish": null,
|
"publish": {
|
||||||
|
"provider": "github",
|
||||||
|
"owner": "lone-cloud",
|
||||||
|
"repo": "gerbil"
|
||||||
|
},
|
||||||
"electronLanguages": [
|
"electronLanguages": [
|
||||||
"en-US"
|
"en-US"
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -222,9 +222,7 @@ export const App = () => {
|
||||||
/>
|
/>
|
||||||
</AppShell.Main>
|
</AppShell.Main>
|
||||||
|
|
||||||
<AppShell.Footer>
|
<StatusBar />
|
||||||
<StatusBar />
|
|
||||||
</AppShell.Footer>
|
|
||||||
|
|
||||||
<EjectConfirmModal
|
<EjectConfirmModal
|
||||||
opened={ejectConfirmModalOpen}
|
opened={ejectConfirmModalOpen}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Group, useComputedColorScheme } from '@mantine/core';
|
import { Group, useComputedColorScheme, AppShell } from '@mantine/core';
|
||||||
import type {
|
import type {
|
||||||
CpuMetrics,
|
CpuMetrics,
|
||||||
MemoryMetrics,
|
MemoryMetrics,
|
||||||
|
|
@ -24,17 +24,17 @@ export const StatusBar = ({ maxDataPoints = 60 }: StatusBarProps) => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let isMounted = true;
|
let isMounted = true;
|
||||||
|
|
||||||
const handleCpuMetrics = async (metrics: CpuMetrics) => {
|
const handleCpuMetrics = (metrics: CpuMetrics) => {
|
||||||
if (!isMounted) return;
|
if (!isMounted) return;
|
||||||
setCpuMetrics(metrics);
|
setCpuMetrics(metrics);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMemoryMetrics = async (metrics: MemoryMetrics) => {
|
const handleMemoryMetrics = (metrics: MemoryMetrics) => {
|
||||||
if (!isMounted) return;
|
if (!isMounted) return;
|
||||||
setMemoryMetrics(metrics);
|
setMemoryMetrics(metrics);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleGpuMetrics = async (metrics: GpuMetrics) => {
|
const handleGpuMetrics = (metrics: GpuMetrics) => {
|
||||||
if (!isMounted) return;
|
if (!isMounted) return;
|
||||||
setGpuMetrics(metrics);
|
setGpuMetrics(metrics);
|
||||||
};
|
};
|
||||||
|
|
@ -54,44 +54,50 @@ export const StatusBar = ({ maxDataPoints = 60 }: StatusBarProps) => {
|
||||||
}, [maxDataPoints]);
|
}, [maxDataPoints]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group
|
<AppShell.Footer
|
||||||
px="xs"
|
style={{
|
||||||
gap="xs"
|
border: '1px solid var(--mantine-color-default-border)',
|
||||||
justify="flex-end"
|
}}
|
||||||
h="100%"
|
|
||||||
bg={colorScheme === 'dark' ? 'dark.8' : 'gray.1'}
|
|
||||||
>
|
>
|
||||||
{cpuMetrics && memoryMetrics && (
|
<Group
|
||||||
<>
|
px="xs"
|
||||||
<PerformanceBadge
|
gap="xs"
|
||||||
label="CPU"
|
justify="flex-end"
|
||||||
value={`${cpuMetrics.usage}%`}
|
h="100%"
|
||||||
tooltipLabel={`${cpuMetrics.usage}%`}
|
bg={colorScheme === 'dark' ? 'dark.6' : 'gray.1'}
|
||||||
/>
|
>
|
||||||
|
{cpuMetrics && memoryMetrics && (
|
||||||
|
<>
|
||||||
|
<PerformanceBadge
|
||||||
|
label="CPU"
|
||||||
|
value={`${cpuMetrics.usage}%`}
|
||||||
|
tooltipLabel={`${cpuMetrics.usage}%`}
|
||||||
|
/>
|
||||||
|
|
||||||
<PerformanceBadge
|
<PerformanceBadge
|
||||||
label="RAM"
|
label="RAM"
|
||||||
value={`${memoryMetrics.usage}%`}
|
value={`${memoryMetrics.usage}%`}
|
||||||
tooltipLabel={`${memoryMetrics.used.toFixed(2)} GB / ${memoryMetrics.total.toFixed(2)} GB (${memoryMetrics.usage}%)`}
|
tooltipLabel={`${memoryMetrics.used.toFixed(2)} GB / ${memoryMetrics.total.toFixed(2)} GB (${memoryMetrics.usage}%)`}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{gpuMetrics?.gpus.map((gpu, index) => (
|
{gpuMetrics?.gpus.map((gpu, index) => (
|
||||||
<Group gap="xs" key={`gpu-${index}`}>
|
<Group gap="xs" key={`gpu-${index}`}>
|
||||||
<PerformanceBadge
|
<PerformanceBadge
|
||||||
label={`GPU${gpuMetrics.gpus.length > 1 ? ` ${index + 1}` : ''}`}
|
label={`GPU${gpuMetrics.gpus.length > 1 ? ` ${index + 1}` : ''}`}
|
||||||
value={`${gpu.usage}%`}
|
value={`${gpu.usage}%`}
|
||||||
tooltipLabel={`${gpu.usage}%`}
|
tooltipLabel={`${gpu.usage}%`}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PerformanceBadge
|
<PerformanceBadge
|
||||||
label={`VRAM${gpuMetrics.gpus.length > 1 ? ` ${index + 1}` : ''}`}
|
label={`VRAM${gpuMetrics.gpus.length > 1 ? ` ${index + 1}` : ''}`}
|
||||||
value={`${gpu.memoryUsage}%`}
|
value={`${gpu.memoryUsage}%`}
|
||||||
tooltipLabel={`${gpu.memoryUsed.toFixed(2)} GB / ${gpu.memoryTotal.toFixed(2)} GB (${gpu.memoryUsage}%)`}
|
tooltipLabel={`${gpu.memoryUsed.toFixed(2)} GB / ${gpu.memoryTotal.toFixed(2)} GB (${gpu.memoryUsage}%)`}
|
||||||
/>
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
))}
|
))}
|
||||||
</Group>
|
</Group>
|
||||||
|
</AppShell.Footer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -7,19 +7,12 @@ import {
|
||||||
useComputedColorScheme,
|
useComputedColorScheme,
|
||||||
AppShell,
|
AppShell,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import {
|
import { Minus, Square, X, Copy, Settings } from 'lucide-react';
|
||||||
Minus,
|
|
||||||
Square,
|
|
||||||
X,
|
|
||||||
Copy,
|
|
||||||
CircleFadingArrowUp,
|
|
||||||
Settings,
|
|
||||||
} from 'lucide-react';
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useAppUpdateChecker } from '@/hooks/useAppUpdateChecker';
|
|
||||||
import { useInterfaceOptions } from '@/hooks/useInterfaceSelection';
|
import { useInterfaceOptions } from '@/hooks/useInterfaceSelection';
|
||||||
import { useLogoClickSounds } from '@/hooks/useLogoClickSounds';
|
import { useLogoClickSounds } from '@/hooks/useLogoClickSounds';
|
||||||
import { SettingsModal } from '@/components/settings/SettingsModal';
|
import { SettingsModal } from '@/components/settings/SettingsModal';
|
||||||
|
import { UpdateButton } from '@/components/UpdateButton';
|
||||||
import icon from '/icon.png';
|
import icon from '/icon.png';
|
||||||
import { PRODUCT_NAME, TITLEBAR_HEIGHT } from '@/constants';
|
import { PRODUCT_NAME, TITLEBAR_HEIGHT } from '@/constants';
|
||||||
import type { InterfaceTab, Screen } from '@/types';
|
import type { InterfaceTab, Screen } from '@/types';
|
||||||
|
|
@ -40,7 +33,6 @@ export const TitleBar = ({
|
||||||
const computedColorScheme = useComputedColorScheme('light', {
|
const computedColorScheme = useComputedColorScheme('light', {
|
||||||
getInitialValueInEffect: true,
|
getInitialValueInEffect: true,
|
||||||
});
|
});
|
||||||
const { hasUpdate, releaseUrl } = useAppUpdateChecker();
|
|
||||||
const interfaceOptions = useInterfaceOptions();
|
const interfaceOptions = useInterfaceOptions();
|
||||||
const { handleLogoClick, getLogoStyles } = useLogoClickSounds();
|
const { handleLogoClick, getLogoStyles } = useLogoClickSounds();
|
||||||
const [isMaximized, setIsMaximized] = useState(false);
|
const [isMaximized, setIsMaximized] = useState(false);
|
||||||
|
|
@ -61,7 +53,9 @@ export const TitleBar = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppShell.Header style={{ display: 'flex', flexDirection: 'column' }}>
|
<AppShell.Header
|
||||||
|
style={{ display: 'flex', flexDirection: 'column', border: 'none' }}
|
||||||
|
>
|
||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
height: TITLEBAR_HEIGHT,
|
height: TITLEBAR_HEIGHT,
|
||||||
|
|
@ -73,7 +67,7 @@ export const TitleBar = ({
|
||||||
computedColorScheme === 'dark'
|
computedColorScheme === 'dark'
|
||||||
? 'var(--mantine-color-dark-8)'
|
? 'var(--mantine-color-dark-8)'
|
||||||
: 'var(--mantine-color-gray-1)',
|
: 'var(--mantine-color-gray-1)',
|
||||||
borderBottom: '1px solid var(--mantine-color-default-border)',
|
border: '1px solid var(--mantine-color-default-border)',
|
||||||
WebkitAppRegion: isSelectOpen ? 'no-drag' : 'drag',
|
WebkitAppRegion: isSelectOpen ? 'no-drag' : 'drag',
|
||||||
userSelect: 'none',
|
userSelect: 'none',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
|
|
@ -151,25 +145,7 @@ export const TitleBar = ({
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Group gap="0" style={{ WebkitAppRegion: 'no-drag' }}>
|
<Group gap="0" style={{ WebkitAppRegion: 'no-drag' }}>
|
||||||
{hasUpdate && releaseUrl && (
|
<UpdateButton />
|
||||||
<ActionIcon
|
|
||||||
component="a"
|
|
||||||
href={releaseUrl}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
variant="subtle"
|
|
||||||
color="orange"
|
|
||||||
size={TITLEBAR_HEIGHT}
|
|
||||||
aria-label="New release available"
|
|
||||||
tabIndex={-1}
|
|
||||||
style={{
|
|
||||||
borderRadius: 0,
|
|
||||||
margin: 0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CircleFadingArrowUp size="1.25rem" />
|
|
||||||
</ActionIcon>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
|
|
@ -179,7 +155,7 @@ export const TitleBar = ({
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
style={{
|
style={{
|
||||||
borderRadius: 0,
|
borderRadius: 0,
|
||||||
margin: 0,
|
margin: '2px 1px 1px',
|
||||||
outline: 'none',
|
outline: 'none',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -225,7 +201,7 @@ export const TitleBar = ({
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
style={{
|
style={{
|
||||||
borderRadius: 0,
|
borderRadius: 0,
|
||||||
margin: 0,
|
margin: '2px 1px 1px',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{button.icon}
|
{button.icon}
|
||||||
|
|
|
||||||
59
src/components/UpdateButton.tsx
Normal file
59
src/components/UpdateButton.tsx
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
import { ActionIcon } from '@mantine/core';
|
||||||
|
import { CircleFadingArrowUp } from 'lucide-react';
|
||||||
|
import { useAppUpdateChecker } from '@/hooks/useAppUpdateChecker';
|
||||||
|
import { TITLEBAR_HEIGHT } from '@/constants';
|
||||||
|
|
||||||
|
export const UpdateButton = () => {
|
||||||
|
const {
|
||||||
|
hasUpdate,
|
||||||
|
releaseUrl,
|
||||||
|
canAutoUpdate,
|
||||||
|
isUpdateDownloaded,
|
||||||
|
isDownloading,
|
||||||
|
downloadUpdate,
|
||||||
|
installUpdate,
|
||||||
|
} = useAppUpdateChecker();
|
||||||
|
|
||||||
|
if (!hasUpdate) return null;
|
||||||
|
|
||||||
|
const isButton = canAutoUpdate;
|
||||||
|
const isLink = !canAutoUpdate && releaseUrl;
|
||||||
|
|
||||||
|
let color: 'green' | 'blue' | 'orange' = 'orange';
|
||||||
|
let label = 'New release available';
|
||||||
|
let onClick: (() => void) | undefined;
|
||||||
|
|
||||||
|
if (isUpdateDownloaded) {
|
||||||
|
color = 'green';
|
||||||
|
label = 'Install update and restart';
|
||||||
|
onClick = installUpdate;
|
||||||
|
} else if (isDownloading) {
|
||||||
|
color = 'blue';
|
||||||
|
label = 'Downloading update...';
|
||||||
|
} else if (canAutoUpdate) {
|
||||||
|
color = 'blue';
|
||||||
|
label = 'Download and install update';
|
||||||
|
onClick = downloadUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ActionIcon
|
||||||
|
component={(isButton ? 'button' : 'a') as 'button' | 'a'}
|
||||||
|
href={isLink ? releaseUrl : undefined}
|
||||||
|
target={isLink ? '_blank' : undefined}
|
||||||
|
rel={isLink ? 'noopener noreferrer' : undefined}
|
||||||
|
variant="subtle"
|
||||||
|
color={color}
|
||||||
|
size={TITLEBAR_HEIGHT}
|
||||||
|
aria-label={label}
|
||||||
|
tabIndex={-1}
|
||||||
|
onClick={onClick}
|
||||||
|
style={{
|
||||||
|
borderRadius: 0,
|
||||||
|
margin: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CircleFadingArrowUp size="1.25rem" />
|
||||||
|
</ActionIcon>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { Card, Container, Stack, Tabs, Group, Button } from '@mantine/core';
|
import { Card, Container, Stack, Tabs, Group, Button } from '@mantine/core';
|
||||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||||
import { tryExecute, logError, safeExecute } from '@/utils/logger';
|
import { logError, safeExecute } from '@/utils/logger';
|
||||||
import { useLaunchConfig } from '@/hooks/useLaunchConfig';
|
import { useLaunchConfig } from '@/hooks/useLaunchConfig';
|
||||||
import { useLaunchLogic } from '@/hooks/useLaunchLogic';
|
import { useLaunchLogic } from '@/hooks/useLaunchLogic';
|
||||||
import { useWarnings } from '@/hooks/useWarnings';
|
import { useWarnings } from '@/hooks/useWarnings';
|
||||||
|
|
@ -92,17 +92,15 @@ export const LaunchScreen = ({ onLaunch }: LaunchScreenProps) => {
|
||||||
}, [backend, handleBackendChange]);
|
}, [backend, handleBackendChange]);
|
||||||
|
|
||||||
const setInitialDefaults = useCallback(
|
const setInitialDefaults = useCallback(
|
||||||
async (currentModel: string, currentSdModel: string) => {
|
(currentModel: string, currentSdModel: string) => {
|
||||||
await tryExecute(async () => {
|
if (
|
||||||
if (
|
!defaultsSetRef.current &&
|
||||||
!defaultsSetRef.current &&
|
!currentModel.trim() &&
|
||||||
!currentModel.trim() &&
|
!currentSdModel.trim()
|
||||||
!currentSdModel.trim()
|
) {
|
||||||
) {
|
handleModelChange(DEFAULT_MODEL_URL);
|
||||||
handleModelChange(DEFAULT_MODEL_URL);
|
defaultsSetRef.current = true;
|
||||||
defaultsSetRef.current = true;
|
}
|
||||||
}
|
|
||||||
}, 'Failed to set initial defaults:');
|
|
||||||
},
|
},
|
||||||
[handleModelChange]
|
[handleModelChange]
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,11 @@ export const AppearanceTab = () => {
|
||||||
value={colorScheme}
|
value={colorScheme}
|
||||||
onChange={handleColorSchemeChange}
|
onChange={handleColorSchemeChange}
|
||||||
styles={(theme) => ({
|
styles={(theme) => ({
|
||||||
|
root: {
|
||||||
|
border: `0.5px solid ${
|
||||||
|
isDark ? theme.colors.dark[4] : theme.colors.gray[4]
|
||||||
|
}`,
|
||||||
|
},
|
||||||
indicator: {
|
indicator: {
|
||||||
backgroundColor: isDark
|
backgroundColor: isDark
|
||||||
? theme.colors.dark[5]
|
? theme.colors.dark[5]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useState, useCallback, useEffect } from 'react';
|
import { useState, useCallback, useEffect } from 'react';
|
||||||
import { logError } from '@/utils/logger';
|
import { logError, safeExecute } from '@/utils/logger';
|
||||||
import { compareVersions } from '@/utils/version';
|
import { compareVersions } from '@/utils/version';
|
||||||
import { GITHUB_API } from '@/constants';
|
import { GITHUB_API } from '@/constants';
|
||||||
|
|
||||||
|
|
@ -12,12 +12,53 @@ interface AppUpdateInfo {
|
||||||
|
|
||||||
export const useAppUpdateChecker = () => {
|
export const useAppUpdateChecker = () => {
|
||||||
const [updateInfo, setUpdateInfo] = useState<AppUpdateInfo | null>(null);
|
const [updateInfo, setUpdateInfo] = useState<AppUpdateInfo | null>(null);
|
||||||
const [isChecking, setIsChecking] = useState(false);
|
const [canAutoUpdate, setCanAutoUpdate] = useState(false);
|
||||||
const [lastChecked, setLastChecked] = useState<Date | null>(null);
|
const [isUpdateDownloaded, setIsUpdateDownloaded] = useState(false);
|
||||||
|
const [isDownloading, setIsDownloading] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const initializeUpdater = async () => {
|
||||||
|
const [canUpdate, isDownloaded] = await Promise.all([
|
||||||
|
safeExecute(
|
||||||
|
() => window.electronAPI.updater.canAutoUpdate(),
|
||||||
|
'Failed to check auto-update capability'
|
||||||
|
),
|
||||||
|
safeExecute(
|
||||||
|
() => window.electronAPI.updater.isUpdateDownloaded(),
|
||||||
|
'Failed to check update download status'
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (canUpdate !== null) setCanAutoUpdate(canUpdate);
|
||||||
|
if (isDownloaded !== null) setIsUpdateDownloaded(isDownloaded);
|
||||||
|
};
|
||||||
|
|
||||||
|
void initializeUpdater();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const downloadUpdate = useCallback(async () => {
|
||||||
|
if (!canAutoUpdate) return;
|
||||||
|
|
||||||
|
setIsDownloading(true);
|
||||||
|
try {
|
||||||
|
const success = await window.electronAPI.updater.downloadUpdate();
|
||||||
|
if (success) {
|
||||||
|
setIsUpdateDownloaded(true);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
logError('Failed to download update:', err as Error);
|
||||||
|
} finally {
|
||||||
|
setIsDownloading(false);
|
||||||
|
}
|
||||||
|
}, [canAutoUpdate]);
|
||||||
|
|
||||||
|
const installUpdate = useCallback(() => {
|
||||||
|
if (isUpdateDownloaded) {
|
||||||
|
window.electronAPI.updater.quitAndInstall();
|
||||||
|
}
|
||||||
|
}, [isUpdateDownloaded]);
|
||||||
|
|
||||||
const checkForAppUpdates = useCallback(async () => {
|
const checkForAppUpdates = useCallback(async () => {
|
||||||
setIsChecking(true);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const currentVersion = await window.electronAPI.app.getVersion();
|
const currentVersion = await window.electronAPI.app.getVersion();
|
||||||
|
|
||||||
|
|
@ -46,27 +87,22 @@ export const useAppUpdateChecker = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
setUpdateInfo(updateInfo);
|
setUpdateInfo(updateInfo);
|
||||||
setLastChecked(new Date());
|
|
||||||
|
|
||||||
return updateInfo;
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logError('Failed to check for app updates:', err as Error);
|
logError('Failed to check for app updates:', err as Error);
|
||||||
return null;
|
|
||||||
} finally {
|
|
||||||
setIsChecking(false);
|
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
checkForAppUpdates();
|
void checkForAppUpdates();
|
||||||
}, [checkForAppUpdates]);
|
}, [checkForAppUpdates]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
updateInfo,
|
|
||||||
isChecking,
|
|
||||||
lastChecked,
|
|
||||||
checkForAppUpdates,
|
|
||||||
releaseUrl: updateInfo?.releaseUrl,
|
releaseUrl: updateInfo?.releaseUrl,
|
||||||
hasUpdate: updateInfo?.hasUpdate || false,
|
hasUpdate: updateInfo?.hasUpdate || false,
|
||||||
|
canAutoUpdate,
|
||||||
|
isUpdateDownloaded,
|
||||||
|
isDownloading,
|
||||||
|
downloadUpdate,
|
||||||
|
installUpdate,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ export async function initializeApp() {
|
||||||
app.exit(0);
|
app.exit(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.on('will-quit', async (event) => {
|
app.on('will-quit', (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
app.exit(0);
|
app.exit(0);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,12 @@ import {
|
||||||
} from '@/main/modules/binary';
|
} from '@/main/modules/binary';
|
||||||
import { openPerformanceManager } from '@/main/modules/performance';
|
import { openPerformanceManager } from '@/main/modules/performance';
|
||||||
import { startMonitoring, stopMonitoring } from '@/main/modules/monitoring';
|
import { startMonitoring, stopMonitoring } from '@/main/modules/monitoring';
|
||||||
|
import {
|
||||||
|
checkForUpdates,
|
||||||
|
downloadUpdate,
|
||||||
|
quitAndInstall,
|
||||||
|
isUpdateDownloaded,
|
||||||
|
} from '@/main/modules/autoUpdater';
|
||||||
import type { FrontendPreference } from '@/types';
|
import type { FrontendPreference } from '@/types';
|
||||||
import { getAppVersion } from '@/utils/node/fs';
|
import { getAppVersion } from '@/utils/node/fs';
|
||||||
|
|
||||||
|
|
@ -195,10 +201,7 @@ export function setupIPCHandlers() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle('app:minimizeWindow', () => {
|
ipcMain.handle('app:minimizeWindow', () => getMainWindow()?.minimize());
|
||||||
const mainWindow = getMainWindow();
|
|
||||||
mainWindow?.minimize();
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.handle('app:maximizeWindow', () => {
|
ipcMain.handle('app:maximizeWindow', () => {
|
||||||
const mainWindow = getMainWindow();
|
const mainWindow = getMainWindow();
|
||||||
|
|
@ -209,12 +212,9 @@ export function setupIPCHandlers() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle('app:closeWindow', () => {
|
ipcMain.handle('app:closeWindow', () => getMainWindow()?.close());
|
||||||
const mainWindow = getMainWindow();
|
|
||||||
mainWindow?.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.handle('app:getZoomLevel', async () => {
|
ipcMain.handle('app:getZoomLevel', () => {
|
||||||
const mainWindow = getMainWindow();
|
const mainWindow = getMainWindow();
|
||||||
if (mainWindow) {
|
if (mainWindow) {
|
||||||
return mainWindow.webContents.getZoomLevel();
|
return mainWindow.webContents.getZoomLevel();
|
||||||
|
|
@ -263,9 +263,9 @@ export function setupIPCHandlers() {
|
||||||
|
|
||||||
ipcMain.handle('app:openPerformanceManager', () => openPerformanceManager());
|
ipcMain.handle('app:openPerformanceManager', () => openPerformanceManager());
|
||||||
|
|
||||||
ipcMain.handle('logs:logError', (_, message: string, error?: Error) => {
|
ipcMain.handle('logs:logError', (_, message: string, error?: Error) =>
|
||||||
logError(message, error);
|
logError(message, error)
|
||||||
});
|
);
|
||||||
|
|
||||||
ipcMain.handle('dependencies:isNpxAvailable', () => isNpxAvailable());
|
ipcMain.handle('dependencies:isNpxAvailable', () => isNpxAvailable());
|
||||||
|
|
||||||
|
|
@ -278,7 +278,18 @@ export function setupIPCHandlers() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle('monitoring:stop', () => {
|
ipcMain.handle('monitoring:stop', () => stopMonitoring());
|
||||||
stopMonitoring();
|
|
||||||
});
|
ipcMain.handle('app:checkForUpdates', () => checkForUpdates());
|
||||||
|
|
||||||
|
ipcMain.handle('app:downloadUpdate', () => downloadUpdate());
|
||||||
|
|
||||||
|
ipcMain.handle('app:quitAndInstall', () => quitAndInstall());
|
||||||
|
|
||||||
|
ipcMain.handle('app:isUpdateDownloaded', () => isUpdateDownloaded());
|
||||||
|
|
||||||
|
ipcMain.handle(
|
||||||
|
'app:canAutoUpdate',
|
||||||
|
() => process.platform !== 'linux' || Boolean(process.env.APPIMAGE)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
72
src/main/modules/autoUpdater.ts
Normal file
72
src/main/modules/autoUpdater.ts
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
import { autoUpdater } from 'electron-updater';
|
||||||
|
import { app } from 'electron';
|
||||||
|
import { logError } from '@/main/modules/logging';
|
||||||
|
|
||||||
|
export interface UpdateInfo {
|
||||||
|
version: string;
|
||||||
|
releaseNotes?: string;
|
||||||
|
releaseDate: string;
|
||||||
|
size?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateProgress {
|
||||||
|
bytesPerSecond: number;
|
||||||
|
percent: number;
|
||||||
|
transferred: number;
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
let updateDownloaded = false;
|
||||||
|
|
||||||
|
function setupAutoUpdater() {
|
||||||
|
autoUpdater.autoDownload = false;
|
||||||
|
autoUpdater.autoInstallOnAppQuit = true;
|
||||||
|
|
||||||
|
autoUpdater.on('error', (error) => {
|
||||||
|
logError('Auto-updater error:', error);
|
||||||
|
});
|
||||||
|
|
||||||
|
autoUpdater.on('update-downloaded', () => {
|
||||||
|
updateDownloaded = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setupAutoUpdater();
|
||||||
|
|
||||||
|
export async function checkForUpdates() {
|
||||||
|
try {
|
||||||
|
const result = await autoUpdater.checkForUpdates();
|
||||||
|
return result !== null;
|
||||||
|
} catch (error) {
|
||||||
|
logError('Failed to check for updates:', error as Error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function downloadUpdate() {
|
||||||
|
try {
|
||||||
|
await autoUpdater.downloadUpdate();
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
logError('Failed to download update:', error as Error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function quitAndInstall() {
|
||||||
|
if (updateDownloaded) {
|
||||||
|
autoUpdater.quitAndInstall(false, true);
|
||||||
|
} else {
|
||||||
|
logError('No update downloaded to install');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isUpdateDownloaded() {
|
||||||
|
return updateDownloaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
app.on('ready', () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
void checkForUpdates();
|
||||||
|
}, 5000);
|
||||||
|
});
|
||||||
|
|
@ -196,7 +196,7 @@ async function tryVersionManagerPath(
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function tryAddPathToEnv(
|
function tryAddPathToEnv(
|
||||||
env: Record<string, string | undefined>,
|
env: Record<string, string | undefined>,
|
||||||
path: string
|
path: string
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ function getFallbackDataRoot() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getSillyTavernDataRoot() {
|
function getSillyTavernDataRoot() {
|
||||||
if (detectedDataRoot) {
|
if (detectedDataRoot) {
|
||||||
return detectedDataRoot;
|
return detectedDataRoot;
|
||||||
}
|
}
|
||||||
|
|
@ -64,8 +64,8 @@ async function getSillyTavernDataRoot() {
|
||||||
return fallback;
|
return fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getSillyTavernSettingsPath() {
|
function getSillyTavernSettingsPath() {
|
||||||
const dataRoot = await getSillyTavernDataRoot();
|
const dataRoot = getSillyTavernDataRoot();
|
||||||
return join(dataRoot, 'default-user', 'settings.json');
|
return join(dataRoot, 'default-user', 'settings.json');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -80,7 +80,7 @@ async function createNpxProcess(args: string[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ensureSillyTavernSettings() {
|
async function ensureSillyTavernSettings() {
|
||||||
const settingsPath = await getSillyTavernSettingsPath();
|
const settingsPath = getSillyTavernSettingsPath();
|
||||||
|
|
||||||
if (await pathExists(settingsPath)) {
|
if (await pathExists(settingsPath)) {
|
||||||
sendKoboldOutput(`SillyTavern settings found at ${settingsPath}`);
|
sendKoboldOutput(`SillyTavern settings found at ${settingsPath}`);
|
||||||
|
|
@ -167,7 +167,7 @@ async function setupSillyTavernConfig(
|
||||||
isImageMode: boolean
|
isImageMode: boolean
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const configPath = await getSillyTavernSettingsPath();
|
const configPath = getSillyTavernSettingsPath();
|
||||||
let settings: Record<string, unknown> = {};
|
let settings: Record<string, unknown> = {};
|
||||||
|
|
||||||
if (await pathExists(configPath)) {
|
if (await pathExists(configPath)) {
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import type {
|
||||||
LogsAPI,
|
LogsAPI,
|
||||||
DependenciesAPI,
|
DependenciesAPI,
|
||||||
MonitoringAPI,
|
MonitoringAPI,
|
||||||
|
UpdaterAPI,
|
||||||
} from '@/types/electron';
|
} from '@/types/electron';
|
||||||
|
|
||||||
const koboldAPI: KoboldAPI = {
|
const koboldAPI: KoboldAPI = {
|
||||||
|
|
@ -90,6 +91,10 @@ const appAPI: AppAPI = {
|
||||||
openExternal: (url) => ipcRenderer.invoke('app:openExternal', url),
|
openExternal: (url) => ipcRenderer.invoke('app:openExternal', url),
|
||||||
openPerformanceManager: () =>
|
openPerformanceManager: () =>
|
||||||
ipcRenderer.invoke('app:openPerformanceManager'),
|
ipcRenderer.invoke('app:openPerformanceManager'),
|
||||||
|
checkForUpdates: () => ipcRenderer.invoke('app:checkForUpdates'),
|
||||||
|
downloadUpdate: () => ipcRenderer.invoke('app:downloadUpdate'),
|
||||||
|
quitAndInstall: () => ipcRenderer.invoke('app:quitAndInstall'),
|
||||||
|
isUpdateDownloaded: () => ipcRenderer.invoke('app:isUpdateDownloaded'),
|
||||||
};
|
};
|
||||||
|
|
||||||
const configAPI: ConfigAPI = {
|
const configAPI: ConfigAPI = {
|
||||||
|
|
@ -130,6 +135,14 @@ const monitoringAPI: MonitoringAPI = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updaterAPI: UpdaterAPI = {
|
||||||
|
checkForUpdates: () => ipcRenderer.invoke('app:checkForUpdates'),
|
||||||
|
downloadUpdate: () => ipcRenderer.invoke('app:downloadUpdate'),
|
||||||
|
quitAndInstall: () => ipcRenderer.invoke('app:quitAndInstall'),
|
||||||
|
isUpdateDownloaded: () => ipcRenderer.invoke('app:isUpdateDownloaded'),
|
||||||
|
canAutoUpdate: () => ipcRenderer.invoke('app:canAutoUpdate'),
|
||||||
|
};
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld('electronAPI', {
|
contextBridge.exposeInMainWorld('electronAPI', {
|
||||||
kobold: koboldAPI,
|
kobold: koboldAPI,
|
||||||
app: appAPI,
|
app: appAPI,
|
||||||
|
|
@ -137,4 +150,5 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
||||||
logs: logsAPI,
|
logs: logsAPI,
|
||||||
dependencies: dependenciesAPI,
|
dependencies: dependenciesAPI,
|
||||||
monitoring: monitoringAPI,
|
monitoring: monitoringAPI,
|
||||||
|
updater: updaterAPI,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,40 @@
|
||||||
@import '@fontsource/inter/latin-500.css';
|
@import '@fontsource/inter/latin-500.css';
|
||||||
@import '@fontsource/inter/latin-600.css';
|
@import '@fontsource/inter/latin-600.css';
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--mantine-color-body: #fafafa;
|
||||||
|
--mantine-color-white: #fafafa;
|
||||||
|
|
||||||
|
--mantine-color-gray-0: #f8f9fa;
|
||||||
|
--mantine-color-gray-1: #f1f3f4;
|
||||||
|
--mantine-color-gray-2: #e9ecef;
|
||||||
|
--mantine-color-gray-3: #dee2e6;
|
||||||
|
|
||||||
|
--mantine-color-default-border: #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mantine-color-scheme='dark'] {
|
||||||
|
--mantine-color-body: #0f0f0f;
|
||||||
|
--mantine-color-dark-7: #1a1a1a;
|
||||||
|
--mantine-color-dark-6: #2d2d2d;
|
||||||
|
--mantine-color-dark-5: #3d3d3d;
|
||||||
|
|
||||||
|
--mantine-color-default-border: #2a2a2a;
|
||||||
|
}
|
||||||
|
|
||||||
|
#root {
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
border-left: 1px solid rgba(0, 0, 0, 0.15);
|
||||||
|
border-right: 1px solid rgba(0, 0, 0, 0.15);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mantine-color-scheme='dark'] #root {
|
||||||
|
border-left: 1px solid rgba(255, 255, 255, 0.15);
|
||||||
|
border-right: 1px solid rgba(255, 255, 255, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
/* Custom scrollbars */
|
/* Custom scrollbars */
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 0.5rem;
|
width: 0.5rem;
|
||||||
|
|
@ -29,18 +63,16 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Dark mode scrollbar support */
|
/* Dark mode scrollbar support */
|
||||||
@media (prefers-color-scheme: dark) {
|
[data-mantine-color-scheme='dark'] ::-webkit-scrollbar-thumb {
|
||||||
::-webkit-scrollbar-thumb {
|
background: rgba(173, 181, 189, 0.5);
|
||||||
background: rgba(173, 181, 189, 0.5);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb:hover {
|
[data-mantine-color-scheme='dark'] ::-webkit-scrollbar-thumb:hover {
|
||||||
background: rgba(173, 181, 189, 0.8);
|
background: rgba(173, 181, 189, 0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb:active {
|
[data-mantine-color-scheme='dark'] ::-webkit-scrollbar-thumb:active {
|
||||||
background: rgba(206, 212, 218, 0.9);
|
background: rgba(206, 212, 218, 0.9);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TitleBar gerbil icon animations */
|
/* TitleBar gerbil icon animations */
|
||||||
|
|
|
||||||
13
src/types/electron.d.ts
vendored
13
src/types/electron.d.ts
vendored
|
|
@ -166,6 +166,10 @@ export interface AppAPI {
|
||||||
app?: string;
|
app?: string;
|
||||||
error?: string;
|
error?: string;
|
||||||
}>;
|
}>;
|
||||||
|
checkForUpdates: () => Promise<boolean>;
|
||||||
|
downloadUpdate: () => Promise<boolean>;
|
||||||
|
quitAndInstall: () => Promise<void>;
|
||||||
|
isUpdateDownloaded: () => Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ConfigAPI {
|
export interface ConfigAPI {
|
||||||
|
|
@ -193,6 +197,14 @@ export interface MonitoringAPI {
|
||||||
removeGpuMetricsListener: () => void;
|
removeGpuMetricsListener: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UpdaterAPI {
|
||||||
|
checkForUpdates: () => Promise<boolean>;
|
||||||
|
downloadUpdate: () => Promise<boolean>;
|
||||||
|
quitAndInstall: () => void;
|
||||||
|
isUpdateDownloaded: () => Promise<boolean>;
|
||||||
|
canAutoUpdate: () => Promise<boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
electronAPI: {
|
electronAPI: {
|
||||||
|
|
@ -202,6 +214,7 @@ declare global {
|
||||||
logs: LogsAPI;
|
logs: LogsAPI;
|
||||||
dependencies: DependenciesAPI;
|
dependencies: DependenciesAPI;
|
||||||
monitoring: MonitoringAPI;
|
monitoring: MonitoringAPI;
|
||||||
|
updater: UpdaterAPI;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
80
yarn.lock
80
yarn.lock
|
|
@ -1287,12 +1287,12 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@types/node@npm:*, @types/node@npm:^24.5.1":
|
"@types/node@npm:*, @types/node@npm:^24.5.2":
|
||||||
version: 24.5.1
|
version: 24.5.2
|
||||||
resolution: "@types/node@npm:24.5.1"
|
resolution: "@types/node@npm:24.5.2"
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types: "npm:~7.12.0"
|
undici-types: "npm:~7.12.0"
|
||||||
checksum: 10c0/5f0cb038be789b58170e616452ba1f8ebb85bf2fbce58a7e32b1eb08391f64f5e31a9cdbccefbfcd9e6d73b66b564b5e037a1d678ab20213559a32e1d7b6ce17
|
checksum: 10c0/96baaca6564d39c6f7f6eddd73ce41e2a7594ef37225cd52df3be36fad31712af8ae178387a72d0b80f2e2799e7fd30c014bc0ae9eb9f962d9079b691be00c48
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
@ -1976,6 +1976,16 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"builder-util-runtime@npm:9.3.1":
|
||||||
|
version: 9.3.1
|
||||||
|
resolution: "builder-util-runtime@npm:9.3.1"
|
||||||
|
dependencies:
|
||||||
|
debug: "npm:^4.3.4"
|
||||||
|
sax: "npm:^1.2.4"
|
||||||
|
checksum: 10c0/32de87e5f294154de707f40acf59a5600af9d1ce903ccbba53b81824de7a1dd9568c5f0c033ed765e14c4ea73347aac09ecbce686e1bc7fefbd7b4f64d2c9d68
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"builder-util-runtime@npm:9.4.0":
|
"builder-util-runtime@npm:9.4.0":
|
||||||
version: 9.4.0
|
version: 9.4.0
|
||||||
resolution: "builder-util-runtime@npm:9.4.0"
|
resolution: "builder-util-runtime@npm:9.4.0"
|
||||||
|
|
@ -2683,6 +2693,22 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"electron-updater@npm:6.6.2":
|
||||||
|
version: 6.6.2
|
||||||
|
resolution: "electron-updater@npm:6.6.2"
|
||||||
|
dependencies:
|
||||||
|
builder-util-runtime: "npm:9.3.1"
|
||||||
|
fs-extra: "npm:^10.1.0"
|
||||||
|
js-yaml: "npm:^4.1.0"
|
||||||
|
lazy-val: "npm:^1.0.5"
|
||||||
|
lodash.escaperegexp: "npm:^4.1.2"
|
||||||
|
lodash.isequal: "npm:^4.5.0"
|
||||||
|
semver: "npm:^7.6.3"
|
||||||
|
tiny-typed-emitter: "npm:^2.1.0"
|
||||||
|
checksum: 10c0/2b9ae5583b95f6772c4a2515ddba7ba52b65460ab81f09ae4f0b97c7e3d7b7e3d9426775eb9a53d3193bd4c3d5466bf30827c1a6ee75e4aca739c647f6ac46ff
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"electron-vite@npm:^4.0.0":
|
"electron-vite@npm:^4.0.0":
|
||||||
version: 4.0.0
|
version: 4.0.0
|
||||||
resolution: "electron-vite@npm:4.0.0"
|
resolution: "electron-vite@npm:4.0.0"
|
||||||
|
|
@ -2705,16 +2731,16 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"electron@npm:^38.1.1":
|
"electron@npm:^38.1.2":
|
||||||
version: 38.1.1
|
version: 38.1.2
|
||||||
resolution: "electron@npm:38.1.1"
|
resolution: "electron@npm:38.1.2"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@electron/get": "npm:^2.0.0"
|
"@electron/get": "npm:^2.0.0"
|
||||||
"@types/node": "npm:^22.7.7"
|
"@types/node": "npm:^22.7.7"
|
||||||
extract-zip: "npm:^2.0.1"
|
extract-zip: "npm:^2.0.1"
|
||||||
bin:
|
bin:
|
||||||
electron: cli.js
|
electron: cli.js
|
||||||
checksum: 10c0/0b1e5955679de6b9a9a12fa7a51beaaa5dcf15655888670c39e3a49914f8dba580a1f94b46d83e7f8e5dc7fd22c16b130dd5040208d7a86431cf06e16933d9e8
|
checksum: 10c0/63f768e8ac396221db17c280e483ac838067e881ee61eb78c5c8f587017e51c587cc1eb687f29672b5e63c0af8a5d818d721d49aef20bac082544a8cf2d323ea
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
@ -3648,7 +3674,7 @@ __metadata:
|
||||||
"@fontsource/inter": "npm:^5.2.8"
|
"@fontsource/inter": "npm:^5.2.8"
|
||||||
"@mantine/core": "npm:^8.3.1"
|
"@mantine/core": "npm:^8.3.1"
|
||||||
"@mantine/hooks": "npm:^8.3.1"
|
"@mantine/hooks": "npm:^8.3.1"
|
||||||
"@types/node": "npm:^24.5.1"
|
"@types/node": "npm:^24.5.2"
|
||||||
"@types/react": "npm:^19.1.13"
|
"@types/react": "npm:^19.1.13"
|
||||||
"@types/react-dom": "npm:^19.1.9"
|
"@types/react-dom": "npm:^19.1.9"
|
||||||
"@types/yauzl": "npm:^2.10.3"
|
"@types/yauzl": "npm:^2.10.3"
|
||||||
|
|
@ -3657,8 +3683,9 @@ __metadata:
|
||||||
"@vitejs/plugin-react": "npm:^5.0.3"
|
"@vitejs/plugin-react": "npm:^5.0.3"
|
||||||
axios: "npm:^1.12.2"
|
axios: "npm:^1.12.2"
|
||||||
cross-env: "npm:^10.0.0"
|
cross-env: "npm:^10.0.0"
|
||||||
electron: "npm:^38.1.1"
|
electron: "npm:^38.1.2"
|
||||||
electron-builder: "npm:^26.0.12"
|
electron-builder: "npm:^26.0.12"
|
||||||
|
electron-updater: "npm:6.6.2"
|
||||||
electron-vite: "npm:^4.0.0"
|
electron-vite: "npm:^4.0.0"
|
||||||
eslint: "npm:^9.35.0"
|
eslint: "npm:^9.35.0"
|
||||||
eslint-plugin-import: "npm:^2.32.0"
|
eslint-plugin-import: "npm:^2.32.0"
|
||||||
|
|
@ -3678,7 +3705,7 @@ __metadata:
|
||||||
rollup-plugin-visualizer: "npm:^6.0.3"
|
rollup-plugin-visualizer: "npm:^6.0.3"
|
||||||
systeminformation: "npm:^5.27.10"
|
systeminformation: "npm:^5.27.10"
|
||||||
typescript: "npm:^5.9.2"
|
typescript: "npm:^5.9.2"
|
||||||
vite: "npm:^7.1.5"
|
vite: "npm:^7.1.6"
|
||||||
winston: "npm:^3.17.0"
|
winston: "npm:^3.17.0"
|
||||||
winston-daily-rotate-file: "npm:^5.0.0"
|
winston-daily-rotate-file: "npm:^5.0.0"
|
||||||
yauzl: "npm:^3.2.0"
|
yauzl: "npm:^3.2.0"
|
||||||
|
|
@ -4701,6 +4728,20 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"lodash.escaperegexp@npm:^4.1.2":
|
||||||
|
version: 4.1.2
|
||||||
|
resolution: "lodash.escaperegexp@npm:4.1.2"
|
||||||
|
checksum: 10c0/484ad4067fa9119bb0f7c19a36ab143d0173a081314993fe977bd00cf2a3c6a487ce417a10f6bac598d968364f992153315f0dbe25c9e38e3eb7581dd333e087
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"lodash.isequal@npm:^4.5.0":
|
||||||
|
version: 4.5.0
|
||||||
|
resolution: "lodash.isequal@npm:4.5.0"
|
||||||
|
checksum: 10c0/dfdb2356db19631a4b445d5f37868a095e2402292d59539a987f134a8778c62a2810c2452d11ae9e6dcac71fc9de40a6fedcb20e2952a15b431ad8b29e50e28f
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"lodash.merge@npm:4.6.2, lodash.merge@npm:^4.6.2":
|
"lodash.merge@npm:4.6.2, lodash.merge@npm:^4.6.2":
|
||||||
version: 4.6.2
|
version: 4.6.2
|
||||||
resolution: "lodash.merge@npm:4.6.2"
|
resolution: "lodash.merge@npm:4.6.2"
|
||||||
|
|
@ -6231,7 +6272,7 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"semver@npm:7.7.2, semver@npm:^7.3.2, semver@npm:^7.3.5, semver@npm:^7.3.8, semver@npm:^7.5.3, semver@npm:^7.6.0":
|
"semver@npm:7.7.2, semver@npm:^7.3.2, semver@npm:^7.3.5, semver@npm:^7.3.8, semver@npm:^7.5.3, semver@npm:^7.6.0, semver@npm:^7.6.3":
|
||||||
version: 7.7.2
|
version: 7.7.2
|
||||||
resolution: "semver@npm:7.7.2"
|
resolution: "semver@npm:7.7.2"
|
||||||
bin:
|
bin:
|
||||||
|
|
@ -6765,6 +6806,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"tiny-typed-emitter@npm:^2.1.0":
|
||||||
|
version: 2.1.0
|
||||||
|
resolution: "tiny-typed-emitter@npm:2.1.0"
|
||||||
|
checksum: 10c0/522bed4c579ee7ee16548540cb693a3d098b137496110f5a74bff970b54187e6b7343a359b703e33f77c5b4b90ec6cebc0d0ec3dbdf1bd418723c5c3ce36d8a2
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.15":
|
"tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.15":
|
||||||
version: 0.2.15
|
version: 0.2.15
|
||||||
resolution: "tinyglobby@npm:0.2.15"
|
resolution: "tinyglobby@npm:0.2.15"
|
||||||
|
|
@ -7140,9 +7188,9 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"vite@npm:^7.1.5":
|
"vite@npm:^7.1.6":
|
||||||
version: 7.1.5
|
version: 7.1.6
|
||||||
resolution: "vite@npm:7.1.5"
|
resolution: "vite@npm:7.1.6"
|
||||||
dependencies:
|
dependencies:
|
||||||
esbuild: "npm:^0.25.0"
|
esbuild: "npm:^0.25.0"
|
||||||
fdir: "npm:^6.5.0"
|
fdir: "npm:^6.5.0"
|
||||||
|
|
@ -7191,7 +7239,7 @@ __metadata:
|
||||||
optional: true
|
optional: true
|
||||||
bin:
|
bin:
|
||||||
vite: bin/vite.js
|
vite: bin/vite.js
|
||||||
checksum: 10c0/782d2f20c25541b26d1fb39bef5f194149caff39dc25b7836e25f049ca919f2e2ce186bddb21f3f20f6195354b3579ec637a8ca08d65b117f8b6f81e3e730a9c
|
checksum: 10c0/2cd8baec0054956dae61289dd1497109c762cfc27ec6f6b88f39a15d5ddc7e0cc4559b72fbdd701b296c739d2f734d051c28e365539462fb92f83b5e7908f9de
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue