Compare commits

..

2 commits

16 changed files with 224 additions and 98 deletions

View file

@ -149,6 +149,62 @@ jobs:
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
update-flatpak:
needs: release
runs-on: ubuntu-latest
if: needs.release.outputs.is_prerelease != 'true'
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
ref: master
- name: Determine tag name
id: tag
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "tag=${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT
echo "version=${${{ github.event.inputs.tag }}#v}" >> $GITHUB_OUTPUT
else
echo "tag=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
fi
- name: Compute AppImage sha256
id: sha256
run: |
URL="https://github.com/lone-cloud/gerbil/releases/download/${{ steps.tag.outputs.tag }}/Gerbil-${{ steps.tag.outputs.version }}.AppImage"
echo "url=$URL" >> $GITHUB_OUTPUT
SHA=$(curl -sL "$URL" | sha256sum | awk '{print $1}')
echo "sha256=$SHA" >> $GITHUB_OUTPUT
- name: Update flatpak manifest
run: |
sed -i "s|url: https://github.com/lone-cloud/gerbil/releases/download/v[^/]*/Gerbil-.*\.AppImage|url: ${{ steps.sha256.outputs.url }}|" flatpak/app.lonecloud.gerbil.yml
# Only replace the sha256 that follows the AppImage url (not the uv one)
python3 - <<'EOF'
import re, pathlib
p = pathlib.Path('flatpak/app.lonecloud.gerbil.yml')
txt = p.read_text()
txt = re.sub(
r'(url: https://github\.com/lone-cloud/gerbil/releases/download/v[^\n]+\n\s+sha256: )[a-f0-9]{64}',
r'\g<1>${{ steps.sha256.outputs.sha256 }}',
txt
)
p.write_text(txt)
EOF
- name: Commit and push
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add flatpak/app.lonecloud.gerbil.yml
git diff --cached --quiet && echo "No changes" && exit 0
git commit -m "chore: update flatpak manifest for ${{ steps.tag.outputs.tag }}"
git push origin master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
aur-release: aur-release:
needs: release needs: release
if: needs.release.outputs.is_prerelease != 'true' if: needs.release.outputs.is_prerelease != 'true'

View file

@ -11,6 +11,12 @@
[![GitHub stars](https://img.shields.io/github/stars/lone-cloud/gerbil)](https://github.com/lone-cloud/gerbil/stargazers) [![GitHub stars](https://img.shields.io/github/stars/lone-cloud/gerbil)](https://github.com/lone-cloud/gerbil/stargazers)
[![AUR version](https://img.shields.io/aur/version/gerbil)](https://aur.archlinux.org/packages/gerbil) [![AUR version](https://img.shields.io/aur/version/gerbil)](https://aur.archlinux.org/packages/gerbil)
<div style="display: flex; flex-wrap: wrap; justify-content: center; gap: 12px;">
<a href="https://github.com/lone-cloud/gerbil/releases"><img src="assets/badges/github-badge.png" alt="Get it on GitHub" height="50" /></a>
</div>
<br>
[Download](https://github.com/lone-cloud/gerbil/releases/latest) • [Screenshots](#demo--screenshots) • [Installation](#installation) [Download](https://github.com/lone-cloud/gerbil/releases/latest) • [Screenshots](#demo--screenshots) • [Installation](#installation)
</div> </div>

View file

@ -4,11 +4,18 @@ import react from '@vitejs/plugin-react';
import { defineConfig } from 'electron-vite'; import { defineConfig } from 'electron-vite';
import { visualizer } from 'rollup-plugin-visualizer'; import { visualizer } from 'rollup-plugin-visualizer';
const watchIgnore = [resolve(__dirname, 'flatpak/build-dir')];
export default defineConfig({ export default defineConfig({
main: { main: {
build: { build: {
externalizeDeps: true, externalizeDeps: true,
}, },
server: {
watch: {
ignored: watchIgnore,
},
},
resolve: { resolve: {
alias: { alias: {
'@': resolve(__dirname, './src'), '@': resolve(__dirname, './src'),
@ -19,6 +26,11 @@ export default defineConfig({
build: { build: {
externalizeDeps: true, externalizeDeps: true,
}, },
server: {
watch: {
ignored: watchIgnore,
},
},
resolve: { resolve: {
alias: { alias: {
'@': resolve(__dirname, './src'), '@': resolve(__dirname, './src'),
@ -28,6 +40,12 @@ export default defineConfig({
renderer: { renderer: {
root: '.', root: '.',
publicDir: 'src/assets', publicDir: 'src/assets',
server: {
port: 5173,
watch: {
ignored: watchIgnore,
},
},
build: { build: {
outDir: 'dist', outDir: 'dist',
rollupOptions: { rollupOptions: {
@ -51,8 +69,5 @@ export default defineConfig({
template: process.env.ANALYZE === 'server' ? 'network' : 'treemap', template: process.env.ANALYZE === 'server' ? 'network' : 'treemap',
}), }),
].filter(Boolean), ].filter(Boolean),
server: {
port: 5173,
},
}, },
}); });

View file

@ -12,12 +12,13 @@ finish-args:
- --share=ipc - --share=ipc
- --socket=wayland - --socket=wayland
- --socket=fallback-x11 - --socket=fallback-x11
- --socket=pulseaudio
- --device=all - --device=all
- --allow=devel - --allow=devel
- --filesystem=home - --filesystem=home
- --filesystem=host-os - --filesystem=host-os
- --filesystem=/opt/rocm:ro - --filesystem=/opt/rocm:ro
- --talk-name=org.freedesktop.Notifications - --filesystem=/sys/module/amdgpu:ro
- --talk-name=org.freedesktop.Flatpak - --talk-name=org.freedesktop.Flatpak
- --talk-name=org.kde.StatusNotifierWatcher - --talk-name=org.kde.StatusNotifierWatcher
- --talk-name=org.freedesktop.StatusNotifierWatcher - --talk-name=org.freedesktop.StatusNotifierWatcher
@ -47,14 +48,14 @@ modules:
- install -Dm644 squashfs-root/usr/share/icons/hicolor/512x512/apps/gerbil.png /app/share/icons/hicolor/512x512/apps/app.lonecloud.gerbil.png - install -Dm644 squashfs-root/usr/share/icons/hicolor/512x512/apps/gerbil.png /app/share/icons/hicolor/512x512/apps/app.lonecloud.gerbil.png
sources: sources:
- type: file - type: file
url: https://github.com/lone-cloud/gerbil/releases/download/v1.23.45/Gerbil-1.23.45.AppImage path: ../release/Gerbil-1.24.0.AppImage
sha256: b3ea2b4bdbfbf4b44f091a9568deedbdef9cb8cd4d2d5f5be43cbd525b40c16a
- type: script - type: script
dest-filename: gerbil-wrapper.sh dest-filename: gerbil-wrapper.sh
commands: commands:
- export PATH="/run/host/usr/bin:/run/host/usr/local/bin:$HOME/.local/bin:$HOME/.cargo/bin:$PATH" - export PATH="/opt/rocm/bin:/app/bin:/run/host/usr/bin:/run/host/usr/local/bin:$HOME/.local/bin:$HOME/.cargo/bin:$PATH"
- export LD_LIBRARY_PATH="/usr/lib/x86_64-linux-gnu/GL/default/lib:/opt/rocm/lib:/opt/rocm/lib64:/run/host/usr/lib:/run/host/usr/lib/x86_64-linux-gnu:${LD_LIBRARY_PATH}"
- export VK_DRIVER_FILES="/run/host/usr/share/vulkan/icd.d:/run/host/etc/vulkan/icd.d" - export VK_DRIVER_FILES="/run/host/usr/share/vulkan/icd.d:/run/host/etc/vulkan/icd.d"
- exec /app/lib/gerbil/gerbil --no-sandbox "$@" - exec /app/lib/gerbil/gerbil --no-sandbox --use-angle=vulkan "$@"
- type: file - type: file
path: app.lonecloud.gerbil.desktop path: app.lonecloud.gerbil.desktop
- type: file - type: file

View file

@ -1,6 +1,6 @@
{ {
"name": "gerbil", "name": "gerbil",
"version": "1.23.45", "version": "1.24.0",
"description": "Run Large Language Models locally", "description": "Run Large Language Models locally",
"keywords": [ "keywords": [
"ai", "ai",
@ -27,7 +27,6 @@
"check": "oxlint && oxfmt --check", "check": "oxlint && oxfmt --check",
"fix": "oxlint --fix && oxfmt", "fix": "oxlint --fix && oxfmt",
"release": "node --no-warnings scripts/release.ts", "release": "node --no-warnings scripts/release.ts",
"fp:build": "cd flatpak && flatpak-builder --user --force-clean --state-dir=$HOME/.local/share/flatpak-builder/gerbil build-dir app.lonecloud.gerbil.yml",
"fp:install": "cd flatpak && flatpak-builder --user --install --force-clean --state-dir=$HOME/.local/share/flatpak-builder/gerbil build-dir app.lonecloud.gerbil.yml", "fp:install": "cd flatpak && flatpak-builder --user --install --force-clean --state-dir=$HOME/.local/share/flatpak-builder/gerbil build-dir app.lonecloud.gerbil.yml",
"fp:run": "flatpak run app.lonecloud.gerbil", "fp:run": "flatpak run app.lonecloud.gerbil",
"fp:uninstall": "flatpak uninstall --user app.lonecloud.gerbil", "fp:uninstall": "flatpak uninstall --user app.lonecloud.gerbil",
@ -60,7 +59,7 @@
"@uiw/react-codemirror": "^4.25.9", "@uiw/react-codemirror": "^4.25.9",
"@vitejs/plugin-react": "^6.0.1", "@vitejs/plugin-react": "^6.0.1",
"cross-env": "^10.1.0", "cross-env": "^10.1.0",
"electron": "^41.3.0", "electron": "^41.5.0",
"electron-builder": "^26.8.1", "electron-builder": "^26.8.1",
"electron-vite": "^5.0.0", "electron-vite": "^5.0.0",
"jiti": "^2.6.1", "jiti": "^2.6.1",

72
pnpm-lock.yaml generated
View file

@ -82,8 +82,8 @@ importers:
specifier: ^10.1.0 specifier: ^10.1.0
version: 10.1.0 version: 10.1.0
electron: electron:
specifier: ^41.3.0 specifier: ^41.5.0
version: 41.3.0 version: 41.5.0
electron-builder: electron-builder:
specifier: ^26.8.1 specifier: ^26.8.1
version: 26.8.1(electron-builder-squirrel-windows@26.8.1) version: 26.8.1(electron-builder-squirrel-windows@26.8.1)
@ -151,8 +151,8 @@ packages:
resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
'@babel/compat-data@7.29.0': '@babel/compat-data@7.29.3':
resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} resolution: {integrity: sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
'@babel/core@7.29.0': '@babel/core@7.29.0':
@ -201,8 +201,8 @@ packages:
resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==} resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
'@babel/parser@7.29.2': '@babel/parser@7.29.3':
resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} resolution: {integrity: sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
hasBin: true hasBin: true
@ -1157,8 +1157,8 @@ packages:
base64-js@1.5.1: base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
baseline-browser-mapping@2.10.24: baseline-browser-mapping@2.10.25:
resolution: {integrity: sha512-I2NkZOOrj2XuguvWCK6OVh9GavsNjZjK908Rq3mIBK25+GD8vPX5w2WdxVqnQ7xx3SrZJiCiZFu+/Oz50oSYSA==} resolution: {integrity: sha512-QO/VHsXCQdnzADMfmkeOPvHdIAkoB7i0/rGjINPJEetLx75hNttVWGQ/jycHUDP9zZ9rupbm60WRxcwViB0MiA==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
hasBin: true hasBin: true
@ -1449,8 +1449,8 @@ packages:
electron-publish@26.8.1: electron-publish@26.8.1:
resolution: {integrity: sha512-q+jrSTIh/Cv4eGZa7oVR+grEJo/FoLMYBAnSL5GCtqwUpr1T+VgKB/dn1pnzxIxqD8S/jP1yilT9VrwCqINR4w==} resolution: {integrity: sha512-q+jrSTIh/Cv4eGZa7oVR+grEJo/FoLMYBAnSL5GCtqwUpr1T+VgKB/dn1pnzxIxqD8S/jP1yilT9VrwCqINR4w==}
electron-to-chromium@1.5.344: electron-to-chromium@1.5.349:
resolution: {integrity: sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg==} resolution: {integrity: sha512-QsWVGyRuY07Aqb234QytTfwd5d9AJlfNIQ5wIOl1L+PZDzI9d9+Fn0FRale/QYlFxt/bUnB0/nLd1jFPGxGK1A==}
electron-updater@6.8.3: electron-updater@6.8.3:
resolution: {integrity: sha512-Z6sgw3jgbikWKXei1ENdqFOxBP0WlXg3TtKfz0rgw2vIZFJUyI4pD7ZN7jrkm7EoMK+tcm/qTnPUdqfZukBlBQ==} resolution: {integrity: sha512-Z6sgw3jgbikWKXei1ENdqFOxBP0WlXg3TtKfz0rgw2vIZFJUyI4pD7ZN7jrkm7EoMK+tcm/qTnPUdqfZukBlBQ==}
@ -1470,8 +1470,8 @@ packages:
resolution: {integrity: sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==} resolution: {integrity: sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==}
engines: {node: '>=8.0.0'} engines: {node: '>=8.0.0'}
electron@41.3.0: electron@41.5.0:
resolution: {integrity: sha512-2Q5aeocmFdeheZGDUTrAvSR3t+n0c3d104AJWWEnt7syJU0tE4VdibMYaPtQ47QuXSoUf0/xSsfUUvu/uSXIfg==} resolution: {integrity: sha512-x9j9//PubUA4EjDtQbZhtk3prolandqCKgit0uCIqc1jb8FTskPbnJtxcDFB1aejczJcuERgjPixBUaMwoWyJg==}
engines: {node: '>= 12.20.55'} engines: {node: '>= 12.20.55'}
hasBin: true hasBin: true
@ -2208,13 +2208,13 @@ packages:
ms@2.1.3: ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
nanoid@3.3.11: nanoid@3.3.12:
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true hasBin: true
node-abi@4.28.0: node-abi@4.29.0:
resolution: {integrity: sha512-Qfp5XZL1cJDOabOT8H5gnqMTmM4NjvYzHp4I/Kt/Sl76OVkOBBHRFlPspGV0hYvMoqQsypFjT/Yp7Km0beXW9g==} resolution: {integrity: sha512-bGc7hHz6lrdpMqH3XqfiHc5PKzEhjgUj6OLpTXynkLi9JZKyMByI/tdpm4Liu6O2BjtE1lakBWXjOQS1EnSQLQ==}
engines: {node: '>=22.12.0'} engines: {node: '>=22.12.0'}
node-addon-api@1.7.2: node-addon-api@1.7.2:
@ -2333,8 +2333,8 @@ packages:
resolution: {integrity: sha512-ZIfcLJC+7E7FBFnDxm9MPmt7D+DidyQ26lewieO75AdhA2ayMtsJSES0iWzqJQbcVRSrTufQoy0DR94xHue0oA==} resolution: {integrity: sha512-ZIfcLJC+7E7FBFnDxm9MPmt7D+DidyQ26lewieO75AdhA2ayMtsJSES0iWzqJQbcVRSrTufQoy0DR94xHue0oA==}
engines: {node: '>=10.4.0'} engines: {node: '>=10.4.0'}
postcss@8.5.12: postcss@8.5.13:
resolution: {integrity: sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==} resolution: {integrity: sha512-qif0+jGGZoLWdHey3UFHHWP0H7Gbmsk8T5VEqyYFbWqPr1XqvLGBbk/sl8V5exGmcYJklJOhOQq1pV9IcsiFag==}
engines: {node: ^10 || ^12 || >=14} engines: {node: ^10 || ^12 || >=14}
postject@1.0.0-alpha.6: postject@1.0.0-alpha.6:
@ -2991,7 +2991,7 @@ snapshots:
js-tokens: 4.0.0 js-tokens: 4.0.0
picocolors: 1.1.1 picocolors: 1.1.1
'@babel/compat-data@7.29.0': {} '@babel/compat-data@7.29.3': {}
'@babel/core@7.29.0': '@babel/core@7.29.0':
dependencies: dependencies:
@ -3000,7 +3000,7 @@ snapshots:
'@babel/helper-compilation-targets': 7.28.6 '@babel/helper-compilation-targets': 7.28.6
'@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0)
'@babel/helpers': 7.29.2 '@babel/helpers': 7.29.2
'@babel/parser': 7.29.2 '@babel/parser': 7.29.3
'@babel/template': 7.28.6 '@babel/template': 7.28.6
'@babel/traverse': 7.29.0 '@babel/traverse': 7.29.0
'@babel/types': 7.29.0 '@babel/types': 7.29.0
@ -3015,7 +3015,7 @@ snapshots:
'@babel/generator@7.29.1': '@babel/generator@7.29.1':
dependencies: dependencies:
'@babel/parser': 7.29.2 '@babel/parser': 7.29.3
'@babel/types': 7.29.0 '@babel/types': 7.29.0
'@jridgewell/gen-mapping': 0.3.13 '@jridgewell/gen-mapping': 0.3.13
'@jridgewell/trace-mapping': 0.3.31 '@jridgewell/trace-mapping': 0.3.31
@ -3023,7 +3023,7 @@ snapshots:
'@babel/helper-compilation-targets@7.28.6': '@babel/helper-compilation-targets@7.28.6':
dependencies: dependencies:
'@babel/compat-data': 7.29.0 '@babel/compat-data': 7.29.3
'@babel/helper-validator-option': 7.27.1 '@babel/helper-validator-option': 7.27.1
browserslist: 4.28.2 browserslist: 4.28.2
lru-cache: 5.1.1 lru-cache: 5.1.1
@ -3060,7 +3060,7 @@ snapshots:
'@babel/template': 7.28.6 '@babel/template': 7.28.6
'@babel/types': 7.29.0 '@babel/types': 7.29.0
'@babel/parser@7.29.2': '@babel/parser@7.29.3':
dependencies: dependencies:
'@babel/types': 7.29.0 '@babel/types': 7.29.0
@ -3074,7 +3074,7 @@ snapshots:
'@babel/template@7.28.6': '@babel/template@7.28.6':
dependencies: dependencies:
'@babel/code-frame': 7.29.0 '@babel/code-frame': 7.29.0
'@babel/parser': 7.29.2 '@babel/parser': 7.29.3
'@babel/types': 7.29.0 '@babel/types': 7.29.0
'@babel/traverse@7.29.0': '@babel/traverse@7.29.0':
@ -3082,7 +3082,7 @@ snapshots:
'@babel/code-frame': 7.29.0 '@babel/code-frame': 7.29.0
'@babel/generator': 7.29.1 '@babel/generator': 7.29.1
'@babel/helper-globals': 7.28.0 '@babel/helper-globals': 7.28.0
'@babel/parser': 7.29.2 '@babel/parser': 7.29.3
'@babel/template': 7.28.6 '@babel/template': 7.28.6
'@babel/types': 7.29.0 '@babel/types': 7.29.0
debug: 4.4.3 debug: 4.4.3
@ -3223,7 +3223,7 @@ snapshots:
dependencies: dependencies:
'@malept/cross-spawn-promise': 2.0.0 '@malept/cross-spawn-promise': 2.0.0
debug: 4.4.3 debug: 4.4.3
node-abi: 4.28.0 node-abi: 4.29.0
node-api-version: 0.2.1 node-api-version: 0.2.1
node-gyp: 12.3.0 node-gyp: 12.3.0
read-binary-file-arch: 1.0.6 read-binary-file-arch: 1.0.6
@ -3874,7 +3874,7 @@ snapshots:
base64-js@1.5.1: {} base64-js@1.5.1: {}
baseline-browser-mapping@2.10.24: {} baseline-browser-mapping@2.10.25: {}
boolean@3.2.0: boolean@3.2.0:
optional: true optional: true
@ -3894,9 +3894,9 @@ snapshots:
browserslist@4.28.2: browserslist@4.28.2:
dependencies: dependencies:
baseline-browser-mapping: 2.10.24 baseline-browser-mapping: 2.10.25
caniuse-lite: 1.0.30001791 caniuse-lite: 1.0.30001791
electron-to-chromium: 1.5.344 electron-to-chromium: 1.5.349
node-releases: 2.0.38 node-releases: 2.0.38
update-browserslist-db: 1.2.3(browserslist@4.28.2) update-browserslist-db: 1.2.3(browserslist@4.28.2)
@ -4220,7 +4220,7 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
electron-to-chromium@1.5.344: {} electron-to-chromium@1.5.349: {}
electron-updater@6.8.3: electron-updater@6.8.3:
dependencies: dependencies:
@ -4259,7 +4259,7 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
electron@41.3.0: electron@41.5.0:
dependencies: dependencies:
'@electron/get': 2.0.3 '@electron/get': 2.0.3
'@types/node': 24.12.2 '@types/node': 24.12.2
@ -5245,9 +5245,9 @@ snapshots:
ms@2.1.3: {} ms@2.1.3: {}
nanoid@3.3.11: {} nanoid@3.3.12: {}
node-abi@4.28.0: node-abi@4.29.0:
dependencies: dependencies:
semver: 7.7.4 semver: 7.7.4
@ -5411,9 +5411,9 @@ snapshots:
xmlbuilder: 15.1.1 xmlbuilder: 15.1.1
optional: true optional: true
postcss@8.5.12: postcss@8.5.13:
dependencies: dependencies:
nanoid: 3.3.11 nanoid: 3.3.12
picocolors: 1.1.1 picocolors: 1.1.1
source-map-js: 1.2.1 source-map-js: 1.2.1
@ -5925,7 +5925,7 @@ snapshots:
dependencies: dependencies:
lightningcss: 1.32.0 lightningcss: 1.32.0
picomatch: 4.0.4 picomatch: 4.0.4
postcss: 8.5.12 postcss: 8.5.13
rolldown: 1.0.0-rc.17 rolldown: 1.0.0-rc.17
tinyglobby: 0.2.16 tinyglobby: 0.2.16
optionalDependencies: optionalDependencies:

View file

@ -87,11 +87,7 @@ export const StatusBar = () => {
const displayGpuMetrics = systemMonitoringEnabled ? gpuMetrics : null; const displayGpuMetrics = systemMonitoringEnabled ? gpuMetrics : null;
return ( return (
<AppShell.Footer <AppShell.Footer>
style={{
borderTop: '1px solid var(--mantine-color-default-border)',
}}
>
<Group <Group
px="xs" px="xs"
gap="xs" gap="xs"

View file

@ -65,7 +65,7 @@ export const TitleBar = ({ currentScreen, currentTab, onEject, onTabChange }: Ti
}, []); }, []);
return ( return (
<AppShell.Header style={{ border: 'none', display: 'flex', flexDirection: 'column' }}> <AppShell.Header>
<Box <Box
style={{ style={{
WebkitAppRegion: isSelectOpen ? 'no-drag' : 'drag', WebkitAppRegion: isSelectOpen ? 'no-drag' : 'drag',

View file

@ -119,7 +119,7 @@ export const TerminalTab = forwardRef<TerminalTabRef>((_props, ref) => {
color: 'light-dark(var(--mantine-color-dark-filled), var(--mantine-color-gray-0))', color: 'light-dark(var(--mantine-color-dark-filled), var(--mantine-color-gray-0))',
fontFamily: fontFamily:
'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace', 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
fontSize: '0.875em', fontSize: '0.8125em',
lineHeight: 1.4, lineHeight: 1.4,
margin: 0, margin: 0,
opacity: isVisible ? 1 : 0, opacity: isVisible ? 1 : 0,

View file

@ -439,7 +439,7 @@ export const LaunchScreen = ({ onLaunch }: LaunchScreenProps) => {
</Tabs> </Tabs>
<div <div
style={{ style={{
borderTop: '1px solid var(--gerbil-divider)', borderTop: '1px solid var(--mantine-color-default-border)',
padding: 'var(--mantine-spacing-md) 0', padding: 'var(--mantine-spacing-md) 0',
display: 'flex', display: 'flex',
justifyContent: 'flex-end', justifyContent: 'flex-end',

View file

@ -201,9 +201,21 @@ export async function launchKoboldCpp(
await startProxy(koboldHost, koboldPort); await startProxy(koboldHost, koboldPort);
const rocmEnv =
platform !== 'win32'
? {
HSA_OVERRIDE_GFX_VERSION: process.env.HSA_OVERRIDE_GFX_VERSION,
LD_LIBRARY_PATH: ['/opt/rocm/lib', '/opt/rocm/lib64', process.env.LD_LIBRARY_PATH]
.filter(Boolean)
.join(':'),
PATH: ['/opt/rocm/bin', process.env.PATH].filter(Boolean).join(':'),
}
: {};
const child = spawn(currentBackend.path, finalArgs, { const child = spawn(currentBackend.path, finalArgs, {
cwd: binaryDir, cwd: binaryDir,
detached: false, detached: false,
env: { ...process.env, ...rocmEnv },
stdio: ['pipe', 'pipe', 'pipe'], stdio: ['pipe', 'pipe', 'pipe'],
}); });
@ -213,10 +225,6 @@ export async function launchKoboldCpp(
sendKoboldOutput(commandLine); sendKoboldOutput(commandLine);
if (remotetunnel) {
void startTunnel(frontendPreference);
}
let readyResolve: ((value: { success: boolean; pid?: number; error?: string }) => void) | null = let readyResolve: ((value: { success: boolean; pid?: number; error?: string }) => void) | null =
null; null;
let readyReject: ((error: Error) => void) | null = null; let readyReject: ((error: Error) => void) | null = null;
@ -242,6 +250,10 @@ export async function launchKoboldCpp(
} }
readyResolve?.({ pid: child.pid, success: true }); readyResolve?.({ pid: child.pid, success: true });
if (remotetunnel) {
void startTunnel(frontendPreference, true);
}
}; };
const handleOutput = (data: Buffer) => { const handleOutput = (data: Buffer) => {

View file

@ -20,7 +20,10 @@ const replaceKoboldWithGerbil = (data: string) => {
const proxyRequest = (clientReq: IncomingMessage, clientRes: ServerResponse) => { const proxyRequest = (clientReq: IncomingMessage, clientRes: ServerResponse) => {
const options = { const options = {
headers: clientReq.headers, headers: {
...clientReq.headers,
host: `${koboldCppHost}:${koboldCppPort}`,
},
hostname: koboldCppHost, hostname: koboldCppHost,
method: clientReq.method, method: clientReq.method,
path: clientReq.url, path: clientReq.url,

View file

@ -36,19 +36,31 @@ const getCloudflaredAssetName = () => {
} }
}; };
const getCloudflaredDownloadUrl = async () => { const getCloudflaredLatestVersion = async () => {
const response = await fetch(GITHUB_API.CLOUDFLARED_LATEST_RELEASE_URL); const response = await fetch(GITHUB_API.CLOUDFLARED_LATEST_RELEASE_URL);
if (!response.ok) { if (!response.ok) {
throw new Error(`Failed to fetch latest cloudflared release: ${response.statusText}`); throw new Error(`Failed to fetch latest cloudflared release: ${response.statusText}`);
} }
const release = (await response.json()) as { tag_name: string }; const release = (await response.json()) as { tag_name: string };
return GITHUB_API.getCloudflaredDownloadUrl(release.tag_name, getCloudflaredAssetName()); return release.tag_name;
}; };
const downloadCloudflared = async (binPath: string) => { const getCloudflaredDownloadUrl = (version: string) =>
const url = await getCloudflaredDownloadUrl(); GITHUB_API.getCloudflaredDownloadUrl(version, getCloudflaredAssetName());
sendKoboldOutput(`Downloading cloudflared from ${url}...`);
const getInstalledCloudflaredVersion = async (binPath: string) => {
try {
const result = await execa(binPath, ['--version']);
const match = /(\d{4}\.\d+\.\d+)/.exec(result.stdout + result.stderr);
return match ? match[1] : null;
} catch {
return null;
}
};
const downloadCloudflared = async (binPath: string, version: string) => {
const url = getCloudflaredDownloadUrl(version);
sendKoboldOutput(`Downloading cloudflared ${version} from ${url}...`);
const response = await fetch(url); const response = await fetch(url);
if (!response.ok || !response.body) { if (!response.ok || !response.body) {
@ -61,7 +73,7 @@ const downloadCloudflared = async (binPath: string) => {
await chmod(binPath, 0o755); await chmod(binPath, 0o755);
} }
sendKoboldOutput(`Downloaded cloudflared to ${binPath}`); sendKoboldOutput(`Downloaded cloudflared ${version} to ${binPath}`);
}; };
const getTunnelTarget = (frontendPreference: FrontendPreference) => { const getTunnelTarget = (frontendPreference: FrontendPreference) => {
@ -97,7 +109,10 @@ const waitForBackend = async (url: string, timeoutMs = 30_000) => {
return false; return false;
}; };
export const startTunnel = async (frontendPreference: FrontendPreference = 'koboldcpp') => { export const startTunnel = async (
frontendPreference: FrontendPreference = 'koboldcpp',
skipBackendCheck = false,
) => {
if (activeTunnel) { if (activeTunnel) {
return tunnelUrl; return tunnelUrl;
} }
@ -105,6 +120,7 @@ export const startTunnel = async (frontendPreference: FrontendPreference = 'kobo
try { try {
const tunnelTarget = getTunnelTarget(frontendPreference); const tunnelTarget = getTunnelTarget(frontendPreference);
if (!skipBackendCheck) {
sendKoboldOutput('Waiting for backend to be ready...'); sendKoboldOutput('Waiting for backend to be ready...');
const backendReady = await waitForBackend(tunnelTarget); const backendReady = await waitForBackend(tunnelTarget);
@ -113,20 +129,43 @@ export const startTunnel = async (frontendPreference: FrontendPreference = 'kobo
'Backend not ready after 30 seconds. Start your backend first before enabling tunnel.', 'Backend not ready after 30 seconds. Start your backend first before enabling tunnel.',
); );
} }
}
sendKoboldOutput('Starting Cloudflare tunnel...'); sendKoboldOutput(`Starting Cloudflare tunnel → ${tunnelTarget}`);
const bin = getCloudflaredBin(); const bin = getCloudflaredBin();
const latestVersion = await getCloudflaredLatestVersion();
const binExists = await access(bin) const binExists = await access(bin)
.then(() => true) .then(() => true)
.catch(() => false); .catch(() => false);
if (!binExists) { if (binExists) {
await downloadCloudflared(bin); const installedVersion = await getInstalledCloudflaredVersion(bin);
const normalizedInstalled = installedVersion?.replace(/^(\d{4}\.\d+\.\d+).*$/, '$1');
const normalizedLatest = latestVersion.replace(/^[v]?(\d{4}\.\d+\.\d+).*$/, '$1');
if (normalizedInstalled !== normalizedLatest) {
sendKoboldOutput(
`Updating cloudflared ${installedVersion ?? 'unknown'}${latestVersion}`,
);
await downloadCloudflared(bin, latestVersion);
} else {
sendKoboldOutput(`cloudflared ${installedVersion} is up to date`);
}
} else {
await downloadCloudflared(bin, latestVersion);
} }
const tunnel = execa(bin, ['tunnel', '--url', tunnelTarget, '--no-autoupdate']); const nullDevice = platform === 'win32' ? 'NUL' : '/dev/null';
const tunnel = execa(bin, [
'tunnel',
'--config',
nullDevice,
'--url',
tunnelTarget,
'--no-autoupdate',
]);
tunnel.catch(() => {});
activeTunnel = tunnel; activeTunnel = tunnel;
@ -134,19 +173,25 @@ export const startTunnel = async (frontendPreference: FrontendPreference = 'kobo
let output = ''; let output = '';
let urlFound = false; let urlFound = false;
tunnel.stderr?.on('data', (data: Buffer) => { const onTunnelOutput = (text: string) => {
const text = data.toString();
output += text; output += text;
if (text.includes('429') || text.includes('Too Many Requests')) { if (text.includes('429') || text.includes('Too Many Requests')) {
rateLimited = true; rateLimited = true;
} }
}); const match = /https:\/\/[a-z0-9-]+\.trycloudflare\.com/.exec(text);
if (match && match[0] !== tunnelUrl) {
tunnelUrl = match[0];
if (urlFound) {
sendKoboldOutput(`Tunnel URL: ${tunnelUrl}`);
}
sendToRenderer('tunnel-url-changed', tunnelUrl);
}
};
tunnel.stdout?.on('data', (data: Buffer) => { tunnel.stderr?.on('data', (data: Buffer) => onTunnelOutput(data.toString()));
output += data.toString(); tunnel.stdout?.on('data', (data: Buffer) => onTunnelOutput(data.toString()));
});
const url = await new Promise<string>((resolve, reject) => { await new Promise<void>((resolve, reject) => {
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
const message = rateLimited const message = rateLimited
? 'Cloudflare rate limit exceeded. Please wait a few minutes and try again.' ? 'Cloudflare rate limit exceeded. Please wait a few minutes and try again.'
@ -155,17 +200,14 @@ export const startTunnel = async (frontendPreference: FrontendPreference = 'kobo
reject(new Error(message)); reject(new Error(message));
}, 30_000); }, 30_000);
const checkForUrl = () => { const checkInterval = setInterval(() => {
const match = /https:\/\/[a-z0-9-]+\.trycloudflare\.com/.exec(output); if (tunnelUrl) {
if (match) {
urlFound = true; urlFound = true;
clearTimeout(timeout); clearTimeout(timeout);
clearInterval(checkInterval); clearInterval(checkInterval);
resolve(match[0]); resolve();
} }
}; }, 100);
const checkInterval = setInterval(checkForUrl, 100);
tunnel.once('error', (error) => { tunnel.once('error', (error) => {
clearTimeout(timeout); clearTimeout(timeout);
@ -182,11 +224,7 @@ export const startTunnel = async (frontendPreference: FrontendPreference = 'kobo
}); });
}); });
await new Promise((resolve) => setTimeout(resolve, 3000));
tunnelUrl = url;
sendKoboldOutput(`Tunnel ready at ${tunnelUrl}`); sendKoboldOutput(`Tunnel ready at ${tunnelUrl}`);
sendToRenderer('tunnel-url-changed', tunnelUrl);
tunnel.on('error', (error: Error) => { tunnel.on('error', (error: Error) => {
logError(`Tunnel error: ${error.message}`, error); logError(`Tunnel error: ${error.message}`, error);

View file

@ -97,6 +97,7 @@ export async function startFrontend(args: string[]) {
const envConfig: Record<string, string> = { const envConfig: Record<string, string> = {
DATA_DIR: openWebUIDataDir, DATA_DIR: openWebUIDataDir,
OPENSSL_CONF: '/dev/null',
ENABLE_OLLAMA_API: 'false', ENABLE_OLLAMA_API: 'false',
ENABLE_VERSION_UPDATE_CHECK: 'false', ENABLE_VERSION_UPDATE_CHECK: 'false',
GLOBAL_LOG_LEVEL: 'warning', GLOBAL_LOG_LEVEL: 'warning',

View file

@ -14,7 +14,6 @@
:root { :root {
--gerbil-window-border: light-dark(rgba(0, 0, 0, 0.15), rgba(255, 255, 255, 0.08)); --gerbil-window-border: light-dark(rgba(0, 0, 0, 0.15), rgba(255, 255, 255, 0.08));
--gerbil-surface-secondary: light-dark(var(--mantine-color-gray-1), var(--mantine-color-dark-6)); --gerbil-surface-secondary: light-dark(var(--mantine-color-gray-1), var(--mantine-color-dark-6));
--gerbil-divider: light-dark(var(--mantine-color-default-border), oklch(30% 0.009 240));
--gerbil-scrollbar-thumb: color-mix(in srgb, var(--mantine-color-gray-5) 50%, transparent); --gerbil-scrollbar-thumb: color-mix(in srgb, var(--mantine-color-gray-5) 50%, transparent);
--gerbil-scrollbar-thumb-hover: color-mix(in srgb, var(--mantine-color-gray-5) 80%, transparent); --gerbil-scrollbar-thumb-hover: color-mix(in srgb, var(--mantine-color-gray-5) 80%, transparent);
--gerbil-scrollbar-thumb-active: color-mix(in srgb, var(--mantine-color-gray-7) 90%, transparent); --gerbil-scrollbar-thumb-active: color-mix(in srgb, var(--mantine-color-gray-7) 90%, transparent);

View file

@ -86,7 +86,7 @@ export const cssVariablesResolver: CSSVariablesResolver = (t) => {
variables: { ...v8.variables }, variables: { ...v8.variables },
dark: { dark: {
...v8.dark, ...v8.dark,
'--mantine-color-body': 'oklch(18% 0.008 240)', '--mantine-color-body': 'oklch(22% 0.008 240)',
'--mantine-color-default-border': 'oklch(48% 0.009 240)', '--mantine-color-default-border': 'oklch(48% 0.009 240)',
'--gerbil-link-color': 'var(--mantine-color-brand-4)', '--gerbil-link-color': 'var(--mantine-color-brand-4)',
}, },