From 1a62aa744f3de28d9e9f89be811937ee0c30ca7b Mon Sep 17 00:00:00 2001 From: Can Ur Date: Mon, 3 Aug 2026 17:02:40 +0200 Subject: [PATCH 1/2] Mac Intel Support --- .github/workflows/build.yml | 23 ++++++++----- README.md | 67 +++++++++++++++++++------------------ docs/SIGNING.md | 5 +-- package.json | 1 + scripts/make-styled-dmg.sh | 29 +++++++++++----- scripts/updater-manifest.sh | 6 ++-- src-tauri/src/inference.rs | 35 ++++++++++++++++--- 7 files changed, 104 insertions(+), 62 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index db5b493..788807f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,13 +33,15 @@ env: jobs: macos: - name: macOS DMG + name: macOS universal DMG runs-on: macos-latest steps: - uses: actions/checkout@v4 - uses: oven-sh/setup-bun@v2 - uses: dtolnay/rust-toolchain@stable + with: + targets: aarch64-apple-darwin,x86_64-apple-darwin - uses: swatinem/rust-cache@v2 with: workspaces: src-tauri @@ -49,18 +51,21 @@ jobs: - id: updater uses: ./.github/actions/updater-flags - # These three mirror the `build` script in package.json. They are spelled out - # here only so the updater `--config` flag can be injected into the middle - # one; keep them in step with that script. + # Keep this in step with `build:macos-universal`. The updater config must be + # inserted into the Tauri invocation, so CI spells the command out here. - run: bun run typecheck - - run: bun run tauri build --bundles app ${{ steps.updater.outputs.config }} - - run: bun run dmg + - run: bun run tauri build --bundles app --target universal-apple-darwin ${{ steps.updater.outputs.config }} + - name: Verify universal app binary + run: | + lipo "src-tauri/target/universal-apple-darwin/release/bundle/macos/ÆTHER.app/Contents/MacOS/aether" \ + -verify_arch arm64 x86_64 + - run: AETHER_TAURI_TARGET=universal-apple-darwin bun run dmg - uses: actions/upload-artifact@v4 with: name: aether-macos-dmg retention-days: 14 - path: src-tauri/target/release/bundle/dmg/*.dmg + path: src-tauri/target/universal-apple-darwin/release/bundle/dmg/*.dmg if-no-files-found: error # The in-app updater installs this tarball, not the DMG: it replaces the @@ -71,8 +76,8 @@ jobs: name: aether-macos-updater retention-days: 14 path: | - src-tauri/target/release/bundle/macos/*.app.tar.gz - src-tauri/target/release/bundle/macos/*.app.tar.gz.sig + src-tauri/target/universal-apple-darwin/release/bundle/macos/*.app.tar.gz + src-tauri/target/universal-apple-darwin/release/bundle/macos/*.app.tar.gz.sig if-no-files-found: error linux: diff --git a/README.md b/README.md index a237118..a175369 100644 --- a/README.md +++ b/README.md @@ -37,18 +37,18 @@ Download the latest build for your platform from **[Releases](https://github.com/CanPixel/aether/releases/latest)**: -| Platform | File | -| -------------------------- | -------------------------------------------- | -| macOS (Apple Silicon, 11+) | `AETHER_macOS.dmg` | -| Windows (x86_64) | `AETHER_x64-setup.exe` | -| Linux (x86_64) | `AETHER_amd64.deb` · `AETHER_amd64.AppImage` | -| Linux (ARM64) | `AETHER_arm64.deb` | +| Platform | File | +| ----------------------------------- | -------------------------------------------- | +| macOS (Apple Silicon or Intel, 11+) | `AETHER_macOS.dmg` | +| Windows (x86_64) | `AETHER_x64-setup.exe` | +| Linux (x86_64) | `AETHER_amd64.deb` · `AETHER_amd64.AppImage` | +| Linux (ARM64) | `AETHER_arm64.deb` | > [!NOTE] -> **Intel Macs are not supported.** Releases are built `arm64` only, and Rosetta -> translates Intel binaries to Apple Silicon — not the other way round — so there is -> no way to run this DMG on an Intel Mac. Building `universal-apple-darwin` would fix -> it at the cost of doubling an already slow llama.cpp compile. +> The macOS DMG contains a universal binary for Apple Silicon and Intel Macs. Intel +> machines with 16 GB RAM, a quad-core CPU, and an SSD are supported for MiST and +> AiON LiTE. AiON WiSE and large contexts are likely to be slow on Intel hardware; +> Apple Silicon is strongly recommended for the full local-AI experience. > [!IMPORTANT] > **Releases are not yet code-signed**, so your OS will block the first launch. This @@ -719,7 +719,7 @@ LINUX_DOCKER_PLATFORM=linux/amd64 LINUX_TARGET=x86_64-unknown-linux-gnu LINUX_AR Because Tauri cannot cross-compile desktop targets, and llama.cpp builds natively per-OS, the cross-platform installers are produced by `.github/workflows/build.yml`: -- `macos-latest` → styled `.dmg` installer. +- `macos-latest` → universal Apple Silicon + Intel styled `.dmg` installer. - `windows-latest` → NSIS `.exe` / MSI installer. - `ubuntu-latest` → x86_64 `.deb` and AppImage. - `ubuntu-24.04-arm` → ARM64 `.deb` for devices such as Raspberry Pi / Compute Module boards. @@ -757,28 +757,29 @@ bun run build ## Project Scripts -| Script | Purpose | -| ----------------------------- | ------------------------------------------------------------------------------------------------ | -| `bun run dev` | Start the Tauri desktop app in development. | -| `bun run start` | Alias for the Tauri desktop development app. | -| `bun run dev:vite` | Start only the Vite renderer dev server on `127.0.0.1:1420`. | -| `bun run format` | Format the project with Prettier. | -| `bun run typecheck:web` | Run renderer TypeScript checks. | -| `bun run typecheck:tauri` | Run Rust `cargo check` for the Tauri backend. | -| `bun run typecheck` | Run renderer TypeScript checks and Rust `cargo check` for the Tauri backend. | -| `bun run version:bump 1.2.3` | Sync `package.json`, `src-tauri/Cargo.toml`, and `src-tauri/tauri.conf.json` to one app version. | -| `bun run version:check` | Verify the app version manifests match `package.json`. | -| `bun run lint` | Run ESLint. | -| `bun run build:vite` | Build only the Vite renderer assets into `dist/`. | -| `bun run build` | Typecheck and build the Tauri desktop app. | -| `bun run build:desktop-local` | Build local desktop packages for the current Tauri target plus Docker Linux arm64/x64 packages. | -| `bun run android:dev` | Run the Tauri Android app on a connected device or emulator. | -| `bun run android:build:apk` | Build an Android APK. | -| `bun run android:build:aab` | Build an Android App Bundle. | -| `bun run linux:arm64:build` | Build an Ubuntu arm64 Tauri package in Docker. | -| `bun run linux:x64:build` | Build an Ubuntu x86_64 Tauri package in Docker. | -| `bun run linux:arm64:deb` | Build an Ubuntu arm64 `.deb` package in Docker. | -| `bun run linux:x64:deb` | Build an Ubuntu x86_64 `.deb` package in Docker. | +| Script | Purpose | +| ------------------------------- | ------------------------------------------------------------------------------------------------ | +| `bun run dev` | Start the Tauri desktop app in development. | +| `bun run start` | Alias for the Tauri desktop development app. | +| `bun run dev:vite` | Start only the Vite renderer dev server on `127.0.0.1:1420`. | +| `bun run format` | Format the project with Prettier. | +| `bun run typecheck:web` | Run renderer TypeScript checks. | +| `bun run typecheck:tauri` | Run Rust `cargo check` for the Tauri backend. | +| `bun run typecheck` | Run renderer TypeScript checks and Rust `cargo check` for the Tauri backend. | +| `bun run version:bump 1.2.3` | Sync `package.json`, `src-tauri/Cargo.toml`, and `src-tauri/tauri.conf.json` to one app version. | +| `bun run version:check` | Verify the app version manifests match `package.json`. | +| `bun run lint` | Run ESLint. | +| `bun run build:vite` | Build only the Vite renderer assets into `dist/`. | +| `bun run build` | Typecheck and build the Tauri desktop app. | +| `bun run build:macos-universal` | Typecheck and build the universal Apple Silicon + Intel macOS app and DMG. | +| `bun run build:desktop-local` | Build local desktop packages for the current Tauri target plus Docker Linux arm64/x64 packages. | +| `bun run android:dev` | Run the Tauri Android app on a connected device or emulator. | +| `bun run android:build:apk` | Build an Android APK. | +| `bun run android:build:aab` | Build an Android App Bundle. | +| `bun run linux:arm64:build` | Build an Ubuntu arm64 Tauri package in Docker. | +| `bun run linux:x64:build` | Build an Ubuntu x86_64 Tauri package in Docker. | +| `bun run linux:arm64:deb` | Build an Ubuntu arm64 `.deb` package in Docker. | +| `bun run linux:x64:deb` | Build an Ubuntu x86_64 `.deb` package in Docker. | ## Build Outputs diff --git a/docs/SIGNING.md b/docs/SIGNING.md index 313d5d3..307e98d 100644 --- a/docs/SIGNING.md +++ b/docs/SIGNING.md @@ -157,8 +157,9 @@ Install button reports `unconfigured` — _"This build has no update signing key ```bash curl -sL https://github.com/CanPixel/aether/releases/latest/download/latest.json | jq . ``` - It should list `darwin-aarch64`, `windows-x86_64`, and `linux-x86_64`, each with a - non-empty `signature` and a URL pinned to the release tag. + It should list `darwin-aarch64`, `darwin-x86_64`, `windows-x86_64`, and + `linux-x86_64`, each with a non-empty `signature` and a URL pinned to the release + tag. The two macOS entries intentionally share the universal updater archive. ### What updates, and what does not diff --git a/package.json b/package.json index ba95f78..e466681 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "build:vite": "vite build", "prepare:dmg-background": "bash scripts/prepare-dmg-background.sh", "build": "bun run typecheck && tauri build --bundles app && bun run dmg", + "build:macos-universal": "bun run typecheck && tauri build --bundles app --target universal-apple-darwin && AETHER_TAURI_TARGET=universal-apple-darwin bun run dmg", "dmg": "bash scripts/make-styled-dmg.sh", "android:init": "bash scripts/android.sh init", "android:dev": "bash scripts/android.sh dev", diff --git a/scripts/make-styled-dmg.sh b/scripts/make-styled-dmg.sh index 20ad7cc..30b7129 100755 --- a/scripts/make-styled-dmg.sh +++ b/scripts/make-styled-dmg.sh @@ -18,20 +18,31 @@ # background actually landed before declaring success. # # Usage: bun run dmg (expects `tauri build --bundles app` to have produced the .app) +# AETHER_TAURI_TARGET=universal-apple-darwin bun run dmg +# (expects `tauri build --target universal-apple-darwin`) set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$ROOT" VOLNAME="ÆTHER" -APP="src-tauri/target/release/bundle/macos/${VOLNAME}.app" -OUT_DIR="src-tauri/target/release/bundle/dmg" +if [[ -n "${AETHER_TAURI_TARGET:-}" ]]; then + BUNDLE_DIR="src-tauri/target/${AETHER_TAURI_TARGET}/release/bundle" +else + BUNDLE_DIR="src-tauri/target/release/bundle" +fi +APP="${BUNDLE_DIR}/macos/${VOLNAME}.app" +OUT_DIR="${BUNDLE_DIR}/dmg" VERSION="$(bun -e 'console.log(require("./src-tauri/tauri.conf.json").version)')" -case "$(uname -m)" in - arm64) ARCH="aarch64" ;; - x86_64) ARCH="x64" ;; - *) ARCH="$(uname -m)" ;; -esac +if [[ "${AETHER_TAURI_TARGET:-}" == "universal-apple-darwin" ]]; then + ARCH="universal" +else + case "$(uname -m)" in + arm64) ARCH="aarch64" ;; + x86_64) ARCH="x64" ;; + *) ARCH="$(uname -m)" ;; + esac +fi OUT="${OUT_DIR}/${VOLNAME}_${VERSION}_${ARCH}.dmg" BG_PNG="build/dmg-background.png" BG_TIFF="build/dmg-background.tiff" @@ -43,7 +54,7 @@ bash scripts/prepare-dmg-background.sh # Derive everything from tauri.conf.json so the DMG never drifts from config. SPEC="$(mktemp -u -t aether-appdmg).json" trap 'rm -f "$SPEC" 2>/dev/null || true' EXIT -SPEC="$SPEC" bun -e ' +BUNDLE_DIR="$BUNDLE_DIR" SPEC="$SPEC" bun -e ' const fs = require("fs"); const path = require("path"); const conf = require("./src-tauri/tauri.conf.json"); @@ -60,7 +71,7 @@ SPEC="$SPEC" bun -e ' window: { size: { width: d.windowSize.width, height: d.windowSize.height } }, contents: [ { x: d.appPosition.x, y: d.appPosition.y, type: "file", - path: path.resolve("src-tauri/target/release/bundle/macos/" + conf.productName + ".app") }, + path: path.resolve(process.env.BUNDLE_DIR + "/macos/" + conf.productName + ".app") }, { x: d.applicationFolderPosition.x, y: d.applicationFolderPosition.y, type: "link", path: "/Applications" }, ], }; diff --git a/scripts/updater-manifest.sh b/scripts/updater-manifest.sh index 78390cd..b2e53c8 100755 --- a/scripts/updater-manifest.sh +++ b/scripts/updater-manifest.sh @@ -38,10 +38,8 @@ if [[ -z "$mac_sig$win_sig$linux_sig" ]]; then exit 0 fi -# Both macOS arches map to the same tarball: the bundle is built on Apple Silicon, -# and an Intel Mac running it under Rosetta still reports darwin-x86_64 to the -# updater. Drop darwin-x86_64 here if a real Intel or universal build is ever -# published as a separate asset. +# The macOS updater archive contains a universal app binary, so both native +# architectures correctly resolve to the same signed tarball. jq -n \ --arg version "$version" \ --arg pub_date "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ diff --git a/src-tauri/src/inference.rs b/src-tauri/src/inference.rs index cfd6c08..d7d1949 100644 --- a/src-tauri/src/inference.rs +++ b/src-tauri/src/inference.rs @@ -321,12 +321,13 @@ impl NativeModelRuntime { } else { backend.supports_mmap() }; - let mut params = LlamaModelParams::default().with_use_mmap(use_mmap); let use_gpu = match kind { NativeModelKind::Chat => local_gpu_enabled(), NativeModelKind::Embedding => embedding_gpu_enabled(), }; - if use_gpu && backend.supports_gpu_offload() { + let using_gpu = use_gpu && backend.supports_gpu_offload(); + let mut params = LlamaModelParams::default().with_use_mmap(use_mmap); + if using_gpu { params = params.with_n_gpu_layers(999); } else { params = params @@ -334,9 +335,33 @@ impl NativeModelRuntime { .with_devices(&[]) .map_err(|error| format!("Failed to select CPU model backend: {error}"))?; } - let model = LlamaModel::load_from_file(backend, &path, ¶ms).map_err(|error| { - format!("Failed to load local model {}: {error}", model_label(&path)) - })?; + let model = match LlamaModel::load_from_file(backend, &path, ¶ms) { + Ok(model) => model, + // Intel Macs can expose a Metal device with too little VRAM for a full + // model offload. Keep local intelligence usable instead of turning an + // otherwise recoverable GPU allocation failure into a dead model picker. + Err(gpu_error) if using_gpu => { + let cpu_params = LlamaModelParams::default() + .with_use_mmap(use_mmap) + .with_n_gpu_layers(0) + .with_devices(&[]) + .map_err(|error| format!("Failed to select CPU model backend: {error}"))?; + LlamaModel::load_from_file(backend, &path, &cpu_params).map_err( + |cpu_error| { + format!( + "Failed to load local model {} with GPU ({gpu_error}) or CPU fallback ({cpu_error})", + model_label(&path) + ) + }, + )? + } + Err(error) => { + return Err(format!( + "Failed to load local model {}: {error}", + model_label(&path) + )); + } + }; *slot = Some(LoadedNativeModel { path, model }); Ok(()) } From d0709a6adfd8396142bdfd341a1596d2c703a306 Mon Sep 17 00:00:00 2001 From: Can Ur Date: Mon, 3 Aug 2026 17:03:59 +0200 Subject: [PATCH 2/2] prettier --- src-tauri/tauri.conf.json | 60 +++++------------------ src/renderer/src/components/Dashboard.tsx | 4 +- 2 files changed, 12 insertions(+), 52 deletions(-) diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 7edde95..900a7ec 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -26,26 +26,11 @@ "csp": { "default-src": "'self'", "script-src": "'self'", - "style-src": [ - "'self'", - "'unsafe-inline'" - ], - "img-src": [ - "'self'", - "data:", - "blob:" - ], + "style-src": ["'self'", "'unsafe-inline'"], + "img-src": ["'self'", "data:", "blob:"], "font-src": "'self'", - "connect-src": [ - "'self'", - "ipc:", - "http://ipc.localhost" - ], - "media-src": [ - "'self'", - "data:", - "blob:" - ], + "connect-src": ["'self'", "ipc:", "http://ipc.localhost"], + "media-src": ["'self'", "data:", "blob:"], "object-src": "'none'", "base-uri": "'self'", "form-action": "'none'", @@ -53,20 +38,9 @@ }, "devCsp": { "default-src": "'self'", - "script-src": [ - "'self'", - "'unsafe-inline'", - "'unsafe-eval'" - ], - "style-src": [ - "'self'", - "'unsafe-inline'" - ], - "img-src": [ - "'self'", - "data:", - "blob:" - ], + "script-src": ["'self'", "'unsafe-inline'", "'unsafe-eval'"], + "style-src": ["'self'", "'unsafe-inline'"], + "img-src": ["'self'", "data:", "blob:"], "font-src": "'self'", "connect-src": [ "'self'", @@ -76,11 +50,7 @@ "ws://localhost:1420", "http://127.0.0.1:1420" ], - "media-src": [ - "'self'", - "data:", - "blob:" - ], + "media-src": ["'self'", "data:", "blob:"], "object-src": "'none'", "base-uri": "'self'", "form-action": "'none'" @@ -89,9 +59,7 @@ }, "plugins": { "updater": { - "endpoints": [ - "https://github.com/CanPixel/aether/releases/latest/download/latest.json" - ], + "endpoints": ["https://github.com/CanPixel/aether/releases/latest/download/latest.json"], "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDQ5QzUwQTk2MkJFQjA4MEIKUldRTENPc3JsZ3JGU1FWamQrMkUrb0hmSDR2MktHZjltZVJyb0RCcHQzNFdEelIxNlYzdmlvOFgK", "windows": { "installMode": "passive" @@ -100,14 +68,8 @@ }, "bundle": { "active": true, - "targets": [ - "app" - ], - "icon": [ - "../resources/icon.png", - "../build/icon.icns", - "../build/icon.ico" - ], + "targets": ["app"], + "icon": ["../resources/icon.png", "../build/icon.icns", "../build/icon.ico"], "macOS": { "minimumSystemVersion": "10.15", "dmg": { diff --git a/src/renderer/src/components/Dashboard.tsx b/src/renderer/src/components/Dashboard.tsx index c8007cd..971f11c 100644 --- a/src/renderer/src/components/Dashboard.tsx +++ b/src/renderer/src/components/Dashboard.tsx @@ -937,9 +937,7 @@ function CaptureCard({ {capture.provenance.requestedUrl && capture.provenance.requestedUrl !== capture.url && (
Requested
-
- {capture.provenance.requestedUrl} -
+
{capture.provenance.requestedUrl}
)} {capture.provenance.author && (