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,28 +120,52 @@ export const startTunnel = async (frontendPreference: FrontendPreference = 'kobo
try { try {
const tunnelTarget = getTunnelTarget(frontendPreference); const tunnelTarget = getTunnelTarget(frontendPreference);
sendKoboldOutput('Waiting for backend to be ready...'); if (!skipBackendCheck) {
const backendReady = await waitForBackend(tunnelTarget); sendKoboldOutput('Waiting for backend to be ready...');
const backendReady = await waitForBackend(tunnelTarget);
if (!backendReady) { if (!backendReady) {
throw new Error( throw new Error(
'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)',
}, },