Merge pull request #190 from moxin-org/dev

Main update 8/2
This commit is contained in:
Jorge Bejar 2024-08-02 18:03:33 -03:00 committed by GitHub
commit 7615f83e42
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 1642 additions and 737 deletions

View File

@ -8,11 +8,13 @@ on:
push:
branches:
- 'main'
- 'dev'
paths-ignore:
- "**/*.md"
pull_request:
branches:
- 'main'
- 'dev'
paths-ignore:
- "**/*.md"
@ -23,7 +25,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-22.04, ubuntu-20.04]
rust: [1.79, 1.78, 1.77]
rust: [1.79]
env:
LD_LIBRARY_PATH: ~\.wasmedge\lib
@ -45,9 +47,13 @@ jobs:
with:
toolchain: ${{ matrix.rust }}
- name: Test Build
- name: Build
run: |
cargo build --release
cargo build
- name: Build before-packaging-command
run: |
cargo build --manifest-path packaging/before-packaging-command/Cargo.toml
build_macos:
name: MacOS
@ -55,7 +61,7 @@ jobs:
strategy:
matrix:
os: [macos-14, macos-13]
rust: [1.79, 1.78, 1.77]
rust: [1.79]
steps:
- name: Checkout sources
@ -71,16 +77,20 @@ jobs:
with:
toolchain: ${{ matrix.rust }}
- name: Test Build
- name: Build
run: |
cargo build --release
cargo build
- name: Build before-packaging-command
run: |
cargo build --manifest-path packaging/before-packaging-command/Cargo.toml
build_windows:
name: Windows
runs-on: windows-2022
strategy:
matrix:
rust: [1.79, 1.78, 1.77]
rust: [1.79]
env:
WASMEDGE_DIR: ${{ github.workspace }}\WasmEdge-0.14.0-Windows
steps:
@ -108,6 +118,10 @@ jobs:
with:
toolchain: ${{ matrix.rust }}
- name: Test Build
- name: Build
run: |
cargo build
cargo build
- name: Build before-packaging-command
run: |
cargo build --manifest-path packaging/before-packaging-command/Cargo.toml

2
.gitignore vendored
View File

@ -1,4 +1,4 @@
/target
target/
preferences.json
data.*
.vscode

513
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -7,11 +7,13 @@ members = [
"moxin-backend",
"moxin-fake-backend",
]
exclude = ["packaging/before-packaging-command"]
[package]
name = "moxin"
version = "0.1.0"
edition = "2021"
rust-version = "1.79" ## required by cargo-packager
description = "Desktop app for downloading and chatting with AI LLMs"
## Rename the binary to `_moxin_app` to avoid naming conflicts
@ -88,19 +90,7 @@ binaries = [
## In addition, on macOS only, we must download the WasmEdge plugins in order to include them
## in the macOS app bundle. This is because macOS apps must include all dependencies in order to pass notarization.
before-packaging-command = """
mkdir -p ./dist/resources/makepad_widgets/ \
&& cp -r `cargo metadata --format-version 1 | sed -e 's/,\"/,\\n\"/g' | grep 'widgets/Cargo.toml\"' | sed -e 's/\"manifest_path\":\"//g' -e 's/Cargo.toml\",/resources/g'` ./dist/resources/makepad_widgets/ \
&& mkdir -p ./dist/resources/moxin/ \
&& cp -r ./resources ./dist/resources/moxin/; \
\
export CARGO_PACKAGER_HOST_OS=`rustc --print cfg | grep target_os= | sed -e 's/target_os=\"//g' -e 's/\"//g'`; \
echo "\n\nPackaging on host platform '$CARGO_PACKAGER_HOST_OS'\n\n"; \
if [ "$CARGO_PACKAGER_HOST_OS" = macos ]; then \
mkdir -p ./wasmedge \
&& curl -sfL --show-error https://github.com/WasmEdge/WasmEdge/releases/download/0.13.5/WasmEdge-0.13.5-darwin_arm64.tar.gz | bsdtar -xf- -C ./wasmedge \
&& mkdir -p ./wasmedge/WasmEdge-0.13.5-Darwin/plugin \
&& curl -sf --location --progress-bar --show-error https://github.com/WasmEdge/WasmEdge/releases/download/0.13.5/WasmEdge-plugin-wasi_nn-ggml-0.13.5-darwin_arm64.tar.gz | bsdtar -xf- -C ./wasmedge/WasmEdge-0.13.5-Darwin/plugin; \
fi; \
cargo run --manifest-path packaging/before-packaging-command/Cargo.toml before-packaging
"""
## See the above paragraph comments for more info on how we create/populate the below `src` directories.
@ -121,31 +111,11 @@ resources = [
## which is currently `/usr/lib/moxin-runner`.
## This is the directory in which `dpkg` copies app resource files to when installing the `.deb` package.
## * On Linux, we also strip the binaries of unneeded content, as required for Debian packages.
## * For Debian and Pacman packages, we also auto-generate the list of dependencies required by moxin,
## * For Debian and Pacman (still a to-do!) packages, we also auto-generate the list of dependencies required by Moxin,
## making sure to add `curl` since it is used by an invocation in `moxin-runner`.
##
before-each-package-command = """
export CARGO_PACKAGER_HOST_OS=`rustc --print cfg | grep target_os= | sed -e 's/target_os=\"//g' -e 's/\"//g'`; \
echo -e "\n\n================================================\n Packaging '$CARGO_PACKAGER_FORMAT' on host platform '$CARGO_PACKAGER_HOST_OS'\n================================================\n\n"; \
if [ "$CARGO_PACKAGER_HOST_OS" = macos ]; then \
MAKEPAD_PACKAGE_DIR=../Resources cargo build --workspace --release --features macos_bundle \
&& install_name_tool -add_rpath "@executable_path/../Frameworks" ./target/release/_moxin_app; \
elif [ "$CARGO_PACKAGER_HOST_OS" = linux ]; then \
if [ "$CARGO_PACKAGER_FORMAT" = appimage ]; then \
MAKEPAD_PACKAGE_DIR=../../usr/lib/moxin cargo build --workspace --release; \
else \
MAKEPAD_PACKAGE_DIR=/usr/lib/moxin cargo build --workspace --release --features "reqwest/native-tls-vendored"; \
if [ "$CARGO_PACKAGER_FORMAT" = deb ]; then \
for path in $(ldd target/release/_moxin_app | awk '{print $3}'); do \
basename "$path" ; \
done \
| xargs dpkg -S 2> /dev/null | awk '{print $1}' | awk -F ':' '{print $1}' | sort | uniq > ./dist/depends_deb.txt; \
echo "curl" >> ./dist/depends_deb.txt; \
fi; \
fi \
&& strip --strip-unneeded --remove-section=.comment --remove-section=.note target/release/_moxin_app target/release/moxin; \
else exit 2; \
fi; \
cargo run --manifest-path packaging/before-packaging-command/Cargo.toml before-each-package
"""
deep_link_protocols = [
@ -159,18 +129,28 @@ section = "utils"
[package.metadata.packager.appimage]
## `curl` is needed for `moxin-runner` to auto-install wasmedge.
bins = [ "/usr/bin/curl" ]
bins = ["/usr/bin/curl"]
[package.metadata.packager.macos]
minimum_system_version = "11.0"
frameworks = [
"./wasmedge/WasmEdge-0.13.5-Darwin/lib/libwasmedge.0.dylib",
"./wasmedge/WasmEdge-0.13.5-Darwin/plugin/libwasmedgePluginWasiNN.dylib",
"./wasmedge/WasmEdge-0.14.0-Darwin/lib/libwasmedge.0.dylib",
"./wasmedge/WasmEdge-0.14.0-Darwin/plugin/libwasmedgePluginWasiNN.dylib",
]
## Configuration for `cargo packager`'s generation of a macOS `.dmg`.
[package.metadata.packager.dmg]
background = "./packaging/Moxin macOS dmg background.png"
window_size = { width = 960, height = 540 }
app_position = { x = 200, y = 250 }
application_folder_position = { x = 760, y = 250 }
## Configuration for `cargo packager`'s generation of a Windows `.exe` setup installer.
[package.metadata.packager.nsis]
## See this: <https://nsis.sourceforge.io/Docs/Chapter4.html#varconstant>
appdata_paths = [
"$APPDATA/$PUBLISHER/$PRODUCTNAME",
"$LOCALAPPDATA/$PRODUCTNAME",
]

View File

@ -14,29 +14,32 @@ The following table shows which host systems can currently be used to build Moxi
First, [install Rust](https://www.rust-lang.org/tools/install).
Then, install the required WasmEdge WASM runtime:
```sh
curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install_v2.sh | bash -s -- --version=0.14.0
source $HOME/.wasmedge/env
```
Obtain the source code from this repository:
```sh
git clone https://github.com/moxin-org/moxin.git
```
### macOS
Install the required WasmEdge WASM runtime:
```sh
curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install_v2.sh | bash -s -- --version=0.14.0
Then, on a standard desktop platform (macOS), simply run:
source $HOME/.wasmedge/env
```
Then use `cargo` to build and run Moxin:
```sh
cd moxin
cargo run
cargo run --release
```
### Linux
Install the required WasmEdge WASM runtime:
```sh
curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install_v2.sh | bash -s -- --version=0.14.0
source $HOME/.wasmedge/env
```
To build Moxin on Linux, you must install the following dependencies:
`openssl`, `clang`/`libclang`, `binfmt`, `Xcursor`/`X11`, `asound`/`pulse`.
@ -47,17 +50,18 @@ sudo apt-get update
sudo apt-get install libssl-dev pkg-config llvm clang libclang-dev binfmt-support libxcursor-dev libx11-dev libasound2-dev libpulse-dev
```
Then, run:
Then use `cargo` to build and run Moxin:
```sh
cd moxin
cargo run
cargo run --release
```
## Windows (Windows 10 or higher)
1. Download and install the LLVM v17.0.6 release for Windows: [Here is a direct link to LLVM-17.0.6-win64.exe](https://github.com/llvm/llvm-project/releases/download/llvmorg-17.0.6/LLVM-17.0.6-win64.exe), 333MB in size.
> [!IMPORTANT]
> During the setup procedure, make sure to select `Add LLVM to the system PATH for all users` or `for the current user`.
> [!IMPORTANT]
> During the setup procedure, make sure to select `Add LLVM to the system PATH for all users` or `for the current user`.
2. Restart your PC, or log out and log back in, which allows the LLVM path to be properly
* Alternatively you can add the LLVM path `C:\Program Files\LLVM\bin` to your system PATH.
3. Download the [WasmEdge-0.14.0-windows.zip](https://github.com/WasmEdge/WasmEdge/releases/download/0.14.0/WasmEdge-0.14.0-windows.zip) file from [the WasmEdge v0.14.0 release page](https://github.com/WasmEdge/WasmEdge/releases/tag/0.14.0),
@ -75,10 +79,9 @@ cargo run
```
4. Download the WasmEdge WASI-NN plugin here: [WasmEdge-plugin-wasi_nn-ggml-0.14.0-windows_x86_64.zip](https://github.com/WasmEdge/WasmEdge/releases/download/0.14.0/WasmEdge-plugin-wasi_nn-ggml-0.14.0-windows_x86_64.zip) (15.5MB) and extract it to the same directory as above, e.g., `C:\Users\<USERNAME>\WasmEdge-0.14.0-Windows`.
> [!IMPORTANT]
> You will be asked whether you want to replace the files that already exist; select `Replace the files in the destination` when doing so.
To do this quickly in powershell:
> [!IMPORTANT]
> You will be asked whether you want to replace the files that already exist; select `Replace the files in the destination` when doing so.
* To do this quickly in powershell:
```powershell
$ProgressPreference = 'SilentlyContinue' ## makes downloads much faster
Invoke-WebRequest -Uri "https://github.com/WasmEdge/WasmEdge/releases/download/0.14.0/WasmEdge-plugin-wasi_nn-ggml-0.14.0-windows_x86_64.zip" -OutFile "WasmEdge-plugin-wasi_nn-ggml-0.14.0-windows_x86_64.zip"
@ -91,30 +94,49 @@ cargo run
```powershell
$env:WASMEDGE_DIR="$home\WasmEdge-0.14.0-Windows\"
$env:WASMEDGE_PLUGIN_PATH="$home\WasmEdge-0.14.0-Windows\"
cargo run
cargo run --release
```
In Windows `cmd`, you can do this like so:
```batch
set WASMEDGE_DIR=%homedrive%%homepath%\WasmEdge-0.14.0-Windows
set WASMEDGE_PLUGIN_PATH=%homedrive%%homepath%\WasmEdge-0.14.0-Windows
cargo run
cargo run --release
```
In a Unix-like shell on Windows (e.g., GitBash, cygwin, msys2, WSL/WSL2):
```sh
WASMEDGE_DIR=$HOME/WasmEdge-0.14.0-Windows \
WASMEDGE_PLUGIN_PATH=$HOME/WasmEdge-0.14.0-Windows \
cargo run --release
```
## Packaging Moxin for Distribution
Install the version of `cargo-packager` maintained by Project Robius:
Install `cargo-packager`:
```sh
cargo install --force --locked --git https://github.com/project-robius/cargo-packager cargo-packager --branch robius
rustup update stable ## Rust version 1.79 or higher is required
cargo +stable install --force --locked cargo-packager
```
For posterity, these instructions have been tested on `cargo-packager` version 0.10.1, which requires Rust v1.79.
### Packaging for Linux
On a Debian-based Linux distribution (e.g., Ubuntu), you can generate a `.deb` Debian package, an AppImage, and a pacman installation package.
> [!IMPORTANT]
> You can only generate a `.deb` Debian package on a Debian-based Linux distribution, as `dpkg` is needed.
> [!NOTE]
> The `pacman` package has not yet been tested.
Ensure you are in the root `moxin` directory, and then you can use `cargo packager` to generate all three package types at once:
```sh
cargo packager --release --verbose ## --verbose is optional
```
To install the Moxin app from the `.deb`package on a Debian-based Linux distribution (e.g., Ubuntu), run:
```sh
cd dist/
@ -130,6 +152,19 @@ chmod +x moxin_0.1.0_x86_64.AppImage
./moxin_0.1.0_x86_64.AppImage
```
### Packaging for Windows
This can only be run on an actual Windows machine, due to platform restrictions.
First, [follow the above instructions for building on Windows](#windows-windows-10-or-higher).
Ensure you are in the root `moxin` directory, and then you can use `cargo packager` to generate a `setup.exe` file using NSIS:
```sh
WASMEDGE_DIR=path/to/WasmEdge-0.14.0-Windows cargo packager --release --formats nsis --verbose ## --verbose is optional
```
After the command completes, you should see a Windows installer called `moxin_0.1.0_x64-setup` in the `dist/` directory.
Double-click that file to install Moxin on your machine, and then run it as you would a regular application.
### Packaging for macOS
This can only be run on an actual macOS machine, due to platform restrictions.

View File

@ -1,140 +0,0 @@
use rusqlite::Row;
use std::{sync::Arc, vec};
#[derive(Debug, Default, PartialEq, Clone)]
pub enum PendingDownloadsStatus {
#[default]
Downloading,
Paused,
Error,
}
impl PendingDownloadsStatus {
pub fn to_string(&self) -> &str {
match self {
Self::Downloading => "downloading",
Self::Paused => "paused",
Self::Error => "error",
}
}
pub fn from_string(s: &str) -> Self {
match s {
"downloading" => Self::Downloading,
"paused" => Self::Paused,
"error" => Self::Error,
_ => Self::Downloading,
}
}
}
#[derive(Debug, Default, PartialEq, Clone)]
pub struct PendingDownloads {
pub file_id: Arc<String>,
pub progress: f64,
pub status: PendingDownloadsStatus,
}
// TODO I'm not 100% convinced that this is the best way to handle this
// I will attempt to merge PendingDownloads and DownloadedFile into a single table, or
// at least a single struct, to see if that makes more sense
impl PendingDownloads {
pub fn insert_into_db(&self, conn: &rusqlite::Connection) -> rusqlite::Result<()> {
conn.execute(
"INSERT INTO pending_downloads (file_id) VALUES (?1)",
rusqlite::params![self.file_id],
)?;
Ok(())
}
pub fn save_to_db(&self, conn: &rusqlite::Connection) -> rusqlite::Result<()> {
conn.execute(
"UPDATE pending_downloads
SET progress = ?2,
status = ?3
WHERE file_id = ?1",
rusqlite::params![self.file_id, self.progress, self.status.to_string()],
)?;
Ok(())
}
fn exists_by_id(conn: &rusqlite::Connection, id: String) -> rusqlite::Result<bool> {
conn.query_row(
"SELECT EXISTS (SELECT file_id FROM pending_downloads WHERE file_id = ?1)",
[id],
|row| row.get::<_, bool>(0),
)
}
pub fn insert_if_not_exists(
file_id: Arc<String>,
conn: &rusqlite::Connection,
) -> rusqlite::Result<()> {
if !Self::exists_by_id(conn, file_id.to_string())? {
let pending_download = PendingDownloads {
file_id: file_id.into(),
..Default::default()
};
pending_download.insert_into_db(conn)?;
}
Ok(())
}
pub fn remove(file_id: Arc<String>, conn: &rusqlite::Connection) -> rusqlite::Result<()> {
conn.execute(
"DELETE FROM pending_downloads WHERE file_id = ?1",
rusqlite::params![file_id],
)?;
Ok(())
}
fn from_row(row: &Row<'_>) -> rusqlite::Result<Self> {
let status = PendingDownloadsStatus::from_string(row.get::<_, String>(2)?.as_str());
Ok(PendingDownloads {
file_id: Arc::new(row.get(0)?),
progress: row.get(1)?,
status: status,
})
}
pub fn get_all(conn: &rusqlite::Connection) -> rusqlite::Result<Vec<Self>> {
let mut stmt = conn.prepare("SELECT * FROM pending_downloads")?;
let mut rows = stmt.query([])?;
let mut downloads = vec![];
while let Some(row) = rows.next()? {
let item = Self::from_row(row)?;
downloads.push(item);
}
Ok(downloads)
}
}
pub fn create_table_pending_downloads(conn: &rusqlite::Connection) -> rusqlite::Result<()> {
conn.execute_batch(
"BEGIN;
CREATE TABLE IF NOT EXISTS pending_downloads (
file_id TEXT PRIMARY KEY,
progress REAL DEFAULT 0,
status TEXT DEFAULT 'downloading'
);
CREATE INDEX IF NOT EXISTS index_pending_downloads_file_id ON pending_downloads (file_id);
COMMIT;",
)?;
Ok(())
}
pub fn mark_pending_downloads_as_paused(conn: &rusqlite::Connection) -> rusqlite::Result<()> {
conn.execute(
"UPDATE pending_downloads
SET status = 'paused'
WHERE status = 'downloading'",
[],
)?;
Ok(())
}

View File

@ -56,6 +56,7 @@ pub struct DownloadedFile {
#[derive(Clone, Debug, Default)]
pub enum PendingDownloadsStatus {
#[default]
Initializing,
Downloading,
Paused,
Error,

View File

@ -7,6 +7,12 @@ description = "A companion app that runs before Moxin to handle wasmedge configu
[dependencies]
directories = "5.0.1"
[target.'cfg(windows)'.dependencies]
## For running a powershell script, which downloads and extracts/installs WasmEdge
powershell_script = "1.1.0"
## For showing a dialog box modal when the CPU is unsupported.
windows-sys = { version = "0.52", features = ["Win32_UI_WindowsAndMessaging", "Win32_Foundation"] }
[[bin]]
name = "moxin"
path = "src/main.rs"

View File

@ -4,8 +4,8 @@
//! such that the main `moxin` app can locate the wasmedge dylibs and plugin dylibs.
//!
//! First, we discover the wasmedge installation.
//! The standard installation directory is `$HOME/.wasmedge`.
//! The default layout of the wasmedge installation directory is as follows:
//! * The standard installation directory on macOS and Linux is `$HOME/.wasmedge`.
//! * On macOS, the default layout of the wasmedge installation directory is as follows:
//! ----------------------------------------------------
//! $HOME/.wasmedge
//! ├── bin
@ -36,11 +36,10 @@
//! ----------------------------------------------------
//!
//! The key environment variables of interest are those that get set by the wasmedge installer.
//! 1. LIBRARY_PATH=$HOME/.wasmedge/lib
//! 2. C_INCLUDE_PATH=$HOME/.wasmedge/include
//! 3. CPLUS_INCLUDE_PATH=$HOME/.wasmedge/include
//!
//! Of these 3, we only care about the `LIBRARY_PATH`, where the `libwasmedge.0.dylib` is located.
//! 1. WASMEDGE_DIR=$HOME/.wasmedge
//! 2. LIBRARY_PATH=$HOME/.wasmedge/lib
//! 3. C_INCLUDE_PATH=$HOME/.wasmedge/include
//! 4. CPLUS_INCLUDE_PATH=$HOME/.wasmedge/include
//!
//! For loading plugins, we need to discover the plugin path. The plugin path can be set in the following ways:
//!/ * The environment variable "WASMEDGE_PLUGIN_PATH".
@ -54,21 +53,51 @@
//! 2. the wasi-nn plugin `libwasmedgePluginWasiNN.dylib`,
//! which is located in `$HOME/.wasmedge/plugin/libwasmedgePluginWasiNN.dylib`.
//!
//! On Windows and Linux, the concepts are the same, but the file names and
//! directory layout of WasmEdge differ from macOS.
//!
#![cfg_attr(feature = "macos_bundle", allow(unused))]
use std::{
ffi::OsStr,
path::{Path, PathBuf},
process::{Command, Stdio},
process::Command,
};
pub const MOXIN_APP_BINARY: &str = "_moxin_app";
const WASMEDGE_DIR_NAME: &str = ".wasmedge";
const LIB_DIR_NAME: &str = "lib";
const PLUGIN_DIR_NAME: &str = "plugin";
/// The name of the wasmedge installation directory.
const WASMEDGE_ROOT_DIR_NAME: &str = {
#[cfg(any(target_os = "linux", target_os = "macos"))] {
".wasmedge"
}
#[cfg(windows)] {
"WasmEdge-0.14.0-Windows"
}
};
/// The subdirectory within the WasmEdge root directory where the main dylib is located.
const DYLIB_DIR_NAME: &str = {
#[cfg(any(target_os = "linux", target_os = "macos"))] {
"lib"
}
#[cfg(windows)] {
"bin"
}
};
/// The subdirectory within the WasmEdge root directory where the plugin dylibs are located.
fn plugin_dir_path_from_root() -> PathBuf {
#[cfg(any(target_os = "linux", target_os = "macos"))] {
PathBuf::from("plugin")
}
#[cfg(windows)] {
Path::new("lib").join("wasmedge")
}
}
/// The file name of the main WasmEdge dylib.
const WASMEDGE_MAIN_DYLIB: &str = {
#[cfg(target_os = "macos")] {
"libwasmedge.0.dylib"
@ -76,7 +105,11 @@ const WASMEDGE_MAIN_DYLIB: &str = {
#[cfg(target_os = "linux")] {
"libwasmedge.so.0"
}
#[cfg(windows)] {
"wasmedge.dll"
}
};
/// The file name of the Wasi-NN plugin dylib.
const WASMEDGE_WASI_NN_PLUGIN_DYLIB: &str = {
#[cfg(target_os = "macos")] {
"libwasmedgePluginWasiNN.dylib"
@ -84,20 +117,22 @@ const WASMEDGE_WASI_NN_PLUGIN_DYLIB: &str = {
#[cfg(target_os = "linux")] {
"libwasmedgePluginWasiNN.so"
}
#[cfg(windows)] {
"wasmedgePluginWasiNN.dll"
}
};
const ENV_WASMEDGE_DIR: &str = "WASMEDGE_DIR";
#[allow(unused)]
const ENV_WASMEDGE_PLUGIN_PATH: &str = "WASMEDGE_PLUGIN_PATH";
const ENV_PATH: &str = "PATH";
const ENV_C_INCLUDE_PATH: &str = "C_INCLUDE_PATH";
const ENV_CPLUS_INCLUDE_PATH: &str = "CPLUS_INCLUDE_PATH";
const ENV_LIBRARY_PATH: &str = "LIBRARY_PATH";
const ENV_LD_LIBRARY_PATH: &str = {
#[cfg(target_os = "macos")] {
"DYLD_LIBRARY_PATH"
}
#[cfg(target_os = "linux")] {
"LD_LIBRARY_PATH"
}
};
#[cfg(target_os = "macos")]
const ENV_LD_LIBRARY_PATH: &str = "DYLD_LIBRARY_PATH";
#[cfg(target_os = "linux")]
const ENV_LD_LIBRARY_PATH: &str = "LD_LIBRARY_PATH";
#[cfg(target_os = "macos")]
const ENV_DYLD_FALLBACK_LIBRARY_PATH: &str = "DYLD_FALLBACK_LIBRARY_PATH";
@ -132,33 +167,35 @@ fn main() -> std::io::Result<()> {
// Thus, we set the `WASMEDGE_PLUGIN_PATH` environment variable to `../Frameworks`,
// because the run_moxin() function will set the current working directory to `Contents/MacOS/`
// within the app bundle, which is the subdirectory that contains the actual moxin executables.
std::env::set_var("WASMEDGE_PLUGIN_PATH", "../Frameworks");
std::env::set_var(ENV_WASMEDGE_PLUGIN_PATH, "../Frameworks");
println!("Running within a macOS app bundle.
WASMEDGE_PLUGIN_PATH: {:?}",
std::env::var("WASMEDGE_PLUGIN_PATH").ok()
{ENV_WASMEDGE_PLUGIN_PATH}: {:?}",
std::env::var(ENV_WASMEDGE_PLUGIN_PATH).ok()
);
run_moxin().unwrap();
run_moxin(None).unwrap();
Ok(())
}
#[cfg(not(feature = "macos_bundle"))]
fn main() -> std::io::Result<()> {
let (wasmedge_dir_in_use, main_dylib_path, wasi_nn_plugin_path) =
// First, check if the wasmedge installation directory exists in the default location.
existing_wasmedge_default_dir()
// If not, try to find the wasmedge installation directory using environment vars.
.or_else(wasmedge_dir_from_env_vars)
check_cpu_features();
let (wasmedge_root_dir_in_use, main_dylib_path, wasi_nn_plugin_path) =
// First, try to find the wasmedge installation directory using environment vars.
wasmedge_root_dir_from_env_vars()
// If not, check if the wasmedge installation directory exists in the default location.
.or_else(existing_wasmedge_default_dir)
// If we have a wasmedge installation directory, try to find the dylibs within it.
.and_then(|wasmedge_dir| find_wasmedge_dylibs(&wasmedge_dir))
.and_then(|wasmedge_root_dir| find_wasmedge_dylibs_in_dir(&wasmedge_root_dir))
// If we couldn't find the wasmedge directory or the dylibs within an existing directory,
// then we must install wasmedge.
.or_else(|| wasmedge_default_dir_path()
.and_then(|default_path| install_wasmedge(default_path).ok())
// If we successfully installed wasmedge, try to find the dylibs again.
.and_then(find_wasmedge_dylibs)
.and_then(find_wasmedge_dylibs_in_dir)
)
.expect("failed to find or install wasmedge dylibs");
@ -166,52 +203,52 @@ fn main() -> std::io::Result<()> {
wasmedge root dir: {}
wasmedge dylib: {}
wasi_nn plugin: {}",
wasmedge_dir_in_use.display(),
wasmedge_root_dir_in_use.display(),
main_dylib_path.display(),
wasi_nn_plugin_path.display(),
);
apply_env_vars(&wasmedge_dir_in_use);
run_moxin().unwrap();
Ok(())
apply_env_vars(&wasmedge_root_dir_in_use);
run_moxin(main_dylib_path.parent())
}
/// Returns an existing path to the default wasmedge installation directory, i.e., `$HOME/.wasmedge`,
/// but only if it exists.
/// Returns the path to the default wasmedge installation directory, if it exists.
fn existing_wasmedge_default_dir() -> Option<PathBuf> {
wasmedge_default_dir_path().and_then(PathExt::path_if_exists)
wasmedge_default_dir_path()?.path_if_exists()
}
/// Returns the path to where wasmedge is installed by default, i.e., `$HOME/.wasmedge`.
/// Returns the path to where wasmedge is installed by default.
///
/// This does not check if the directory actually exists.
fn wasmedge_default_dir_path() -> Option<PathBuf> {
directories::UserDirs::new()
.map(|user_dirs| user_dirs.home_dir().join(WASMEDGE_DIR_NAME))
.map(|dirs| dirs.home_dir().join(WASMEDGE_ROOT_DIR_NAME))
}
/// Looks for the wasmedge dylib and wasi_nn plugin dylib in the given `wasmedge_dir`.
/// Looks for the wasmedge dylib and wasi_nn plugin dylib in the given `wasmedge_root_dir`.
///
/// The `wasmedge_dir` should be the root directory of the wasmedge installation;
/// The `wasmedge_root_dir` should be the root directory of the wasmedge installation;
/// see the crate-level documentation for more information about the expected layout.
///
/// Returns a tuple of:
/// 1. the wasmedge root directory path
/// 2. the main wasmedge dylib path
/// 3. the wasi_nn plugin dylib path
/// if found in `wasmedge_dir/lib/` and `wasmedge_dir/plugin/`.
fn find_wasmedge_dylibs<P: AsRef<Path>>(wasmedge_dir: P) -> Option<(PathBuf, PathBuf, PathBuf)> {
let main_dylib_path = wasmedge_dir.as_ref()
.join(LIB_DIR_NAME)
/// If all items were found in their expected locations, this returns a tuple of:
/// 1. the wasmedge root directory path,
/// 2. the main wasmedge dylib path,
/// 3. the wasi_nn plugin dylib path.
fn find_wasmedge_dylibs_in_dir<P: AsRef<Path>>(wasmedge_root_dir: P) -> Option<(PathBuf, PathBuf, PathBuf)> {
let main_dylib_path = wasmedge_root_dir.as_ref()
.join(DYLIB_DIR_NAME)
.join(WASMEDGE_MAIN_DYLIB)
.path_if_exists()?;
let wasi_nn_plugin_path = wasmedge_dir.as_ref()
.join(PLUGIN_DIR_NAME)
let wasi_nn_plugin_path = wasmedge_root_dir.as_ref()
.join(plugin_dir_path_from_root())
.join(WASMEDGE_WASI_NN_PLUGIN_DYLIB)
.path_if_exists()?;
Some((wasmedge_dir.as_ref().into(), main_dylib_path, wasi_nn_plugin_path))
Some((wasmedge_root_dir.as_ref().into(), main_dylib_path, wasi_nn_plugin_path))
}
@ -223,8 +260,10 @@ fn find_wasmedge_dylibs<P: AsRef<Path>>(wasmedge_dir: P) -> Option<(PathBuf, Pat
///
/// source $HOME/.wasmedge/env
/// ```
fn install_wasmedge<P: AsRef<Path>>(install_path: P) -> Result<P, std::io::Error> {
println!("Attempting to install wasmedge to: {}", install_path.as_ref().display());
#[cfg(any(target_os = "linux", target_os = "macos"))]
fn install_wasmedge<P: AsRef<Path>>(install_path: P) -> Result<PathBuf, std::io::Error> {
use std::process::Stdio;
println!("Downloading WasmEdge 0.14.0 from GitHub and installing it to {}", install_path.as_ref().display());
let temp_dir = std::env::temp_dir();
let curl_script_cmd = Command::new("curl")
.arg("-s")
@ -259,24 +298,77 @@ fn install_wasmedge<P: AsRef<Path>>(install_path: P) -> Result<P, std::io::Error
apply_env_vars(&install_path);
Ok(install_path)
Ok(install_path.as_ref().to_path_buf())
}
/// Applies the environment variable changes defined by `wasmedge_dir/env`.
/// Installs WasmEdge by calling out to PowerShell to run the Windows installation steps
/// provided in the main Moxin README.
///
/// The `wasmedge_dir` should be the root directory of the wasmedge installation,
/// The given `install_path` is currently ignored, using the [wasmedge_default_dir_path()] instead.
///
/// The PowerShell script we run simply downloads and extracts the main WasmEdge files and the Wasi-NN plugin.
/// ```powershell
/// Invoke-WebRequest -Uri "https://github.com/WasmEdge/WasmEdge/releases/download/0.14.0/WasmEdge-0.14.0-windows.zip" -OutFile "$env:TEMP\WasmEdge-0.14.0-windows.zip"
/// Expand-Archive -Force -Path "$env:TEMP\WasmEdge-0.14.0-windows.zip" -DestinationPath $home
/// Invoke-WebRequest -Uri "https://github.com/WasmEdge/WasmEdge/releases/download/0.14.0/WasmEdge-plugin-wasi_nn-ggml-0.14.0-windows_x86_64.zip" -OutFile "$env:TEMP\WasmEdge-plugin-wasi_nn-ggml-0.14.0-windows_x86_64.zip"
/// Expand-Archive -Force -Path "$env:TEMP\WasmEdge-plugin-wasi_nn-ggml-0.14.0-windows_x86_64.zip" -DestinationPath "$home\WasmEdge-0.14.0-Windows"
/// ```
#[cfg(windows)]
fn install_wasmedge<P: AsRef<Path>>(_install_path: P) -> Result<PathBuf, std::io::Error> {
println!("Downloading and installing WasmEdge 0.14.0 from GitHub.");
let install_wasmedge_ps1 = include_str!("powershell_install_wasmedge.ps1");
match powershell_script::PsScriptBuilder::new()
.non_interactive(true)
.hidden(true) // Don't display a PowerShell window
.print_commands(false) // enable this for debugging
.build()
.run(&install_wasmedge_ps1)
{
Ok(output) => {
if output.success() {
// The wasmedge installation directory is currently forced to the default dir path.
wasmedge_default_dir_path().ok_or_else(
|| std::io::Error::new(std::io::ErrorKind::Other, "BUG: couldn't get WasmEdge default directory path.")
)
} else {
eprintln!("------------- Powershell stdout --------------\n{}", output.stdout().unwrap_or_default());
eprintln!("----------------------------------------------\n");
eprintln!("------------- Powershell stderr --------------\n{}", output.stderr().unwrap_or_default());
eprintln!("----------------------------------------------\n");
Err(std::io::Error::new(std::io::ErrorKind::Other, "The WasmEdge install PowerShell script failed."))
}
}
Err(err) => {
eprintln!("Failed to install wasmedge: {:?}", err);
if let powershell_script::PsError::Powershell(output) = err {
eprintln!("------------- Powershell stdout --------------\n{}", output.stdout().unwrap_or_default());
eprintln!("----------------------------------------------\n");
eprintln!("------------- Powershell stderr --------------\n{}", output.stderr().unwrap_or_default());
eprintln!("----------------------------------------------\n");
}
Err(std::io::Error::new(std::io::ErrorKind::Other, "The WasmEdge install PowerShell script failed."))
}
}
}
/// Applies the environment variable changes defined by `wasmedge_root_dir/env`.
///
/// The `wasmedge_root_dir` should be the root directory of the wasmedge installation,
/// which is typically `$HOME/.wasmedge`.
///
/// This does the following:
/// * Prepends `wasmedge_dir/bin` to `PATH`.
/// * Prepends `wasmedge_dir/lib` to `DYLD_LIBRARY_PATH`, `DYLD_FALLBACK_LIBRARY_PATH`, and `LIBRARY_PATH`.
/// * Prepends `wasmedge_dir/include` to `C_INCLUDE_PATH` and `CPLUS_INCLUDE_PATH`.
/// * Prepends `wasmedge_root_dir/bin` to `PATH`.
/// * Prepends `wasmedge_root_dir/lib` to `DYLD_LIBRARY_PATH`, `DYLD_FALLBACK_LIBRARY_PATH`, and `LIBRARY_PATH`.
/// * Prepends `wasmedge_root_dir/include` to `C_INCLUDE_PATH` and `CPLUS_INCLUDE_PATH`.
///
/// Note that we cannot simply run something like `Command::new("source")...`,
/// because `source` is a shell builtin, and the environment changes would only be visible
/// within that new process's shell instance -- not to this program.
fn apply_env_vars<P: AsRef<Path>>(wasmedge_dir_path: &P) {
#[cfg(any(target_os = "linux", target_os = "macos"))]
fn apply_env_vars<P: AsRef<Path>>(wasmedge_root_dir_path: &P) {
use std::ffi::OsStr;
/// Prepends the given `prefix` to the environment variable with the given `key`.
///
/// If the environment variable `key` is not set, it is set to the `prefix` value alone.
@ -291,36 +383,63 @@ fn apply_env_vars<P: AsRef<Path>>(wasmedge_dir_path: &P) {
}
}
let wasmedge_dir = wasmedge_dir_path.as_ref();
prepend_env_var(ENV_PATH, wasmedge_dir.join("bin"));
prepend_env_var(ENV_C_INCLUDE_PATH, wasmedge_dir.join("include"));
prepend_env_var(ENV_CPLUS_INCLUDE_PATH, wasmedge_dir.join("include"));
prepend_env_var(ENV_LIBRARY_PATH, wasmedge_dir.join("lib"));
prepend_env_var(ENV_LD_LIBRARY_PATH, wasmedge_dir.join("lib"));
let wasmedge_root_dir = wasmedge_root_dir_path.as_ref();
prepend_env_var(ENV_PATH, wasmedge_root_dir.join("bin"));
prepend_env_var(ENV_C_INCLUDE_PATH, wasmedge_root_dir.join("include"));
prepend_env_var(ENV_CPLUS_INCLUDE_PATH, wasmedge_root_dir.join("include"));
prepend_env_var(ENV_LIBRARY_PATH, wasmedge_root_dir.join("lib"));
prepend_env_var(ENV_LD_LIBRARY_PATH, wasmedge_root_dir.join("lib"));
// The DYLD_FALLBACK_LIBRARY_PATH is only used on macOS.
#[cfg(target_os = "macos")]
prepend_env_var(ENV_DYLD_FALLBACK_LIBRARY_PATH, wasmedge_dir.join("lib"));
prepend_env_var(ENV_DYLD_FALLBACK_LIBRARY_PATH, wasmedge_root_dir.join("lib"));
}
/// Applies the environment variables needed for Moxin to find WasmEdge on Windows.
///
/// Currently, this only does the following:
/// * Sets [ENV_WASMEDGE_DIR] and [ENV_WASMEDGE_PLUGIN_PATH] to the given `wasmedge_root_dir_path`.
#[cfg(windows)]
fn apply_env_vars<P: AsRef<Path>>(wasmedge_root_dir_path: &P) {
std::env::set_var(ENV_WASMEDGE_DIR, wasmedge_root_dir_path.as_ref());
std::env::set_var(ENV_WASMEDGE_PLUGIN_PATH, wasmedge_root_dir_path.as_ref());
}
/// Attempts to discover the wasmedge installation directory using environment variables.
fn wasmedge_dir_from_env_vars() -> Option<PathBuf> {
std::env::var_os(ENV_LD_LIBRARY_PATH)
.or_else(|| std::env::var_os(ENV_LIBRARY_PATH))
.or_else(|| std::env::var_os(ENV_C_INCLUDE_PATH))
.or_else(|| std::env::var_os(ENV_CPLUS_INCLUDE_PATH))
.and_then(|lib_path| PathBuf::from(lib_path)
// All four of the above environment variables should point to a child directory
// (either `lib/` or `include/`) within the wasmedge root directory.
.parent()
.and_then(PathExt::path_if_exists)
.map(ToOwned::to_owned)
)
///
/// * On Windows, only the [ENV_WASMEDGE_DIR] environment variable can be used.
/// * On Linux and macOS, all other environment variables are checked.
fn wasmedge_root_dir_from_env_vars() -> Option<PathBuf> {
if let Some(dir) = std::env::var_os(ENV_WASMEDGE_DIR).and_then(PathExt::path_if_exists) {
return Some(dir.into());
}
// Note: we cannot use ENV_WASMEDGE_PLUGIN_PATH here, because it can point to multiple directories,
// e.g., the wasmedge root dir, or one of the subdirectories within it.
#[cfg(any(target_os = "linux", target_os = "macos"))] {
std::env::var_os(ENV_LD_LIBRARY_PATH)
.or_else(|| std::env::var_os(ENV_LIBRARY_PATH))
.or_else(|| std::env::var_os(ENV_C_INCLUDE_PATH))
.or_else(|| std::env::var_os(ENV_CPLUS_INCLUDE_PATH))
.and_then(|lib_path| PathBuf::from(lib_path)
// All four of the above environment variables should point to a child directory
// (either `lib/` or `include/`) within the wasmedge root directory.
.parent()
.and_then(PathExt::path_if_exists)
.map(ToOwned::to_owned)
)
}
#[cfg(windows)] {
None
}
}
/// Runs the `_moxin_app` binary, which must be located in the same directory as this moxin-runner binary.
fn run_moxin() -> std::io::Result<()> {
///
/// An optional path to the directory containing the main WasmEdge dylib can be provided,
/// which is currently only used to set the path on Windows.
fn run_moxin(_main_wasmedge_dylib_dir: Option<&Path>) -> std::io::Result<()> {
let current_exe = std::env::current_exe()?;
let current_exe_dir = current_exe.parent().unwrap();
let args = std::env::args().collect::<Vec<_>>();
@ -332,6 +451,25 @@ fn run_moxin() -> std::io::Result<()> {
args,
);
// On Windows, the MOXIN_APP_BINARY needs to be able to find the WASMEDGE_MAIN_DYLIB (wasmedge.dll),
// so we prepend it to the PATH.
#[cfg(windows)] {
match (std::env::var_os(ENV_PATH), _main_wasmedge_dylib_dir) {
(Some(path), Some(dylib_parent)) => {
println!("Prepending \"{}\" to Windows PATH", dylib_parent.display());
let new_path = std::env::join_paths(
Some(dylib_parent.to_path_buf())
.into_iter()
.chain(std::env::split_paths(&path))
)
.expect("BUG: failed to join paths for the main Moxin binary.");
std::env::set_var(ENV_PATH, &new_path);
}
_ => eprintln!("BUG: failed to set PATH for the main Moxin binary."),
}
}
let _output = Command::new(current_exe_dir.join(MOXIN_APP_BINARY))
.current_dir(current_exe_dir)
.args(args.into_iter().skip(1)) // skip the first arg (the binary name)
@ -340,3 +478,85 @@ fn run_moxin() -> std::io::Result<()> {
Ok(())
}
/// Checks that the current CPU supports AVX512, which is required by the current
/// builds of WasmEdge 0.14.0 on Windows.
///
/// Does nothing for other platforms.
fn check_cpu_features() {
#[cfg(windows)] {
if !is_x86_feature_detected!("avx512f") {
eprintln!("Feature aes: {}", is_x86_feature_detected!("aes"));
eprintln!("Feature pclmulqdq: {}", is_x86_feature_detected!("pclmulqdq"));
eprintln!("Feature rdrand: {}", is_x86_feature_detected!("rdrand"));
eprintln!("Feature rdseed: {}", is_x86_feature_detected!("rdseed"));
eprintln!("Feature tsc: {}", is_x86_feature_detected!("tsc"));
eprintln!("Feature mmx: {}", is_x86_feature_detected!("mmx"));
eprintln!("Feature sse: {}", is_x86_feature_detected!("sse"));
eprintln!("Feature sse2: {}", is_x86_feature_detected!("sse2"));
eprintln!("Feature sse3: {}", is_x86_feature_detected!("sse3"));
eprintln!("Feature ssse3: {}", is_x86_feature_detected!("ssse3"));
eprintln!("Feature sse4.1: {}", is_x86_feature_detected!("sse4.1"));
eprintln!("Feature sse4.2: {}", is_x86_feature_detected!("sse4.2"));
eprintln!("Feature sse4a: {}", is_x86_feature_detected!("sse4a"));
eprintln!("Feature sha: {}", is_x86_feature_detected!("sha"));
eprintln!("Feature avx: {}", is_x86_feature_detected!("avx"));
eprintln!("Feature avx2: {}", is_x86_feature_detected!("avx2"));
eprintln!("Feature avx512f: {}", is_x86_feature_detected!("avx512f"));
eprintln!("Feature avx512cd: {}", is_x86_feature_detected!("avx512cd"));
eprintln!("Feature avx512er: {}", is_x86_feature_detected!("avx512er"));
eprintln!("Feature avx512pf: {}", is_x86_feature_detected!("avx512pf"));
eprintln!("Feature avx512bw: {}", is_x86_feature_detected!("avx512bw"));
eprintln!("Feature avx512dq: {}", is_x86_feature_detected!("avx512dq"));
eprintln!("Feature avx512vl: {}", is_x86_feature_detected!("avx512vl"));
eprintln!("Feature avx512ifma: {}", is_x86_feature_detected!("avx512ifma"));
eprintln!("Feature avx512vbmi: {}", is_x86_feature_detected!("avx512vbmi"));
eprintln!("Feature avx512vpopcntdq: {}", is_x86_feature_detected!("avx512vpopcntdq"));
eprintln!("Feature avx512vbmi2: {}", is_x86_feature_detected!("avx512vbmi2"));
eprintln!("Feature gfni: {}", is_x86_feature_detected!("gfni"));
eprintln!("Feature vaes: {}", is_x86_feature_detected!("vaes"));
eprintln!("Feature vpclmulqdq: {}", is_x86_feature_detected!("vpclmulqdq"));
eprintln!("Feature avx512vnni: {}", is_x86_feature_detected!("avx512vnni"));
eprintln!("Feature avx512bitalg: {}", is_x86_feature_detected!("avx512bitalg"));
eprintln!("Feature avx512bf16: {}", is_x86_feature_detected!("avx512bf16"));
eprintln!("Feature avx512vp2intersect: {}", is_x86_feature_detected!("avx512vp2intersect"));
// eprintln!("Feature avx512fp16: {}", is_x86_feature_detected!("avx512fp16"));
eprintln!("Feature f16c: {}", is_x86_feature_detected!("f16c"));
eprintln!("Feature fma: {}", is_x86_feature_detected!("fma"));
eprintln!("Feature bmi1: {}", is_x86_feature_detected!("bmi1"));
eprintln!("Feature bmi2: {}", is_x86_feature_detected!("bmi2"));
eprintln!("Feature abm: {}", is_x86_feature_detected!("abm"));
eprintln!("Feature lzcnt: {}", is_x86_feature_detected!("lzcnt"));
eprintln!("Feature tbm: {}", is_x86_feature_detected!("tbm"));
eprintln!("Feature popcnt: {}", is_x86_feature_detected!("popcnt"));
eprintln!("Feature fxsr: {}", is_x86_feature_detected!("fxsr"));
eprintln!("Feature xsave: {}", is_x86_feature_detected!("xsave"));
eprintln!("Feature xsaveopt: {}", is_x86_feature_detected!("xsaveopt"));
eprintln!("Feature xsaves: {}", is_x86_feature_detected!("xsaves"));
eprintln!("Feature xsavec: {}", is_x86_feature_detected!("xsavec"));
eprintln!("Feature cmpxchg16b: {}", is_x86_feature_detected!("cmpxchg16b"));
eprintln!("Feature adx: {}", is_x86_feature_detected!("adx"));
eprintln!("Feature rtm: {}", is_x86_feature_detected!("rtm"));
eprintln!("Feature movbe: {}", is_x86_feature_detected!("movbe"));
eprintln!("Feature ermsb: {}", is_x86_feature_detected!("ermsb"));
use windows_sys::Win32::UI::WindowsAndMessaging::{
MessageBoxW, MB_ICONERROR, MB_SETFOREGROUND, MB_TOPMOST,
};
// SAFE: just displaying an Error dialog box; the program will be terminated regardless.
unsafe {
MessageBoxW(
0,
windows_sys::w!(
"This CPU does not support AVX512, which is required by Moxin.\n\n\
The list of supported CPU features has been logged to the console.\
"),
windows_sys::w!("Error: Unsupported CPU!"),
MB_SETFOREGROUND | MB_TOPMOST | MB_ICONERROR,
);
}
panic!("\nError: this CPU does not support AVX512, which is required by Moxin.\n")
}
}
}

View File

@ -0,0 +1,7 @@
$ProgressPreference = 'SilentlyContinue' ## makes downloads much faster
Invoke-WebRequest -Uri "https://github.com/WasmEdge/WasmEdge/releases/download/0.14.0/WasmEdge-0.14.0-windows.zip" -OutFile "$env:TEMP\WasmEdge-0.14.0-windows.zip"
Expand-Archive -Force -Path "$env:TEMP\WasmEdge-0.14.0-windows.zip" -DestinationPath $home
Invoke-WebRequest -Uri "https://github.com/WasmEdge/WasmEdge/releases/download/0.14.0/WasmEdge-plugin-wasi_nn-ggml-0.14.0-windows_x86_64.zip" -OutFile "$env:TEMP\WasmEdge-plugin-wasi_nn-ggml-0.14.0-windows_x86_64.zip"
Expand-Archive -Force -Path "$env:TEMP\WasmEdge-plugin-wasi_nn-ggml-0.14.0-windows_x86_64.zip" -DestinationPath "$home\WasmEdge-0.14.0-Windows"
$ProgressPreference = 'Continue' ## restore default progress bars

View File

@ -0,0 +1,299 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "before-packaging-command"
version = "0.1.0"
dependencies = [
"cargo_metadata",
"directories",
]
[[package]]
name = "bitflags"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
name = "camino"
version = "1.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239"
dependencies = [
"serde",
]
[[package]]
name = "cargo-platform"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc"
dependencies = [
"serde",
]
[[package]]
name = "cargo_metadata"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037"
dependencies = [
"camino",
"cargo-platform",
"semver",
"serde",
"serde_json",
"thiserror",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "directories"
version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-sys"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys",
]
[[package]]
name = "getrandom"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "itoa"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "libc"
version = "0.2.155"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
[[package]]
name = "libredox"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [
"bitflags",
"libc",
]
[[package]]
name = "option-ext"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "proc-macro2"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_users"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891"
dependencies = [
"getrandom",
"libredox",
"thiserror",
]
[[package]]
name = "ryu"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "semver"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
dependencies = [
"serde",
]
[[package]]
name = "serde"
version = "1.0.204"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.204"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.120"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "syn"
version = "2.0.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "1.0.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"

View File

@ -0,0 +1,10 @@
[package]
name = "before-packaging-command"
version = "0.1.0"
edition = "2021"
rust-version = "1.79" ## required by cargo-packager
description = "A small Rust program that is run by cargo-packager's 'before packaging' commands."
[dependencies]
cargo_metadata = "0.18"
directories = "5.0.1"

View File

@ -0,0 +1,479 @@
//! This small program is invoked by cargo-packager during its before-packaging steps.
//!
//! This program must be run from the root of the project directory,
//! which is also where the cargo-packager command must be invoked from.
//!
//! This program runs in two modes, one for each kind of before-packaging step in cargo-packager.
//! It accepts arguments to determine which mode:
//! * Argument `before-packaging`: specifies that the `before-packaging-command` is being run
//! by cargo-packager, which gets executed only *once* before cargo-packager generates any package bundles.
//! * Argument `before-each-package`: specifies that the `before-each-package-command` is being run
//! by cargo-packager, which gets executed multiple times: once for *each* package
//! that cargo-packager is going to generate.
//! * The environment variable `CARGO_PACKAGER_FORMAT` is set by cargo-packager to
//! the declare which package format is about to be generated, which include the values
//! given here: <https://docs.rs/cargo-packager/latest/cargo_packager/enum.PackageFormat.html>.
//! * `app`, `dmg`: for macOS.
//! * `deb`, `appimage`, `pacman`: for Linux.
//! * `nsis`: for Windows; `nsis` generates an installer `setup.exe`.
//! * `wix`: (UNSUPPORTED) for Windows; generates an `.msi` installer package.
//!
//! This program uses the `CARGO_PACKAGER_FORMAT` environment variable to determine
//! which specific build commands and configuration options should be used.
//!
use core::panic;
use std::{ffi::OsStr, fs, path::Path, process::{Command, Stdio}};
use cargo_metadata::MetadataCommand;
/// Returns the value of the `MAKEPAD_PACKAGE_DIR` environment variable
/// that must be set for the given package format.
///
/// * For macOS app bundles, this should be set to `../Resources`.
/// * This only works because the `moxin-runner` binary sets the current working directory
/// to the directory where the binary is located, which is `Moxin.app/Contents/MacOS/`.
/// (See the `run_moxin` function in `moxin-runner/src/main.rs` for more details.)
/// * In a macOS app bundle, the resources directory is in `Moxin.app/Context/Resources/`,
/// so that's why we set `MAKEPAD_PACKAGE_DIR` to `../Resources`.
/// This must be relative to the binary's location, i.e. up one parent directory.
/// * For AppImage packages, this should be set to a relative path that goes up two parent directories
/// to account for the fact that AppImage binaries run in a simulated `usr/bin/` directory.
/// * Thus, we need to get to the simulated `usr/lib/` directory for Moxin's resources,
/// which currently works out to `../../usr/lib/moxin`.
/// * Note that this must be a relative path, not an absolute path.
/// * For Debian `.deb` packages, this should be set to `/usr/lib/<main-binary-name>`,
/// which is currently `/usr/lib/moxin-runner`.
/// * This is the directory in which `dpkg` copies app resource files to
/// when a user installs the `.deb` package.
/// * For Windows NSIS packages, this should be set to `.` (the current dir).
/// * This is because the NSIS installer script copies the resources to the same directory
/// as the installed binaries, and our `moxin-runner` app changes the current working directory
/// to that same directory before running the main Moxin binary.
fn makepad_package_dir_value(package_format: &str) -> &'static str {
match package_format {
"app" | "dmg" => "../Resources",
"appimage" => "../../usr/lib/moxin",
"deb" | "pacman" => "/usr/lib/moxin",
"nsis" => ".",
_other => panic!("Unsupported package format: {}", _other),
}
}
fn main() -> std::io::Result<()> {
let mut is_before_packaging = false;
let mut is_before_each_package = false;
let mut host_os_opt: Option<String> = None;
let mut args = std::env::args().peekable();
while let Some(arg) = args.next() {
if arg.ends_with("before-packaging") || arg.ends_with("before_packaging") {
is_before_packaging = true;
}
if arg.contains("before-each") || arg.contains("before_each") {
is_before_each_package = true;
}
if host_os_opt.is_none() && (arg.contains("host_os") || arg.contains("host-os"))
{
host_os_opt = arg
.split("=")
.last()
.map(|s| s.to_string())
.or_else(|| args.peek().map(|s| s.to_string()));
}
}
let host_os = host_os_opt.as_deref().unwrap_or(std::env::consts::OS);
match (is_before_packaging, is_before_each_package) {
(true, false) => before_packaging(host_os),
(false, true) => before_each_package(host_os),
(true, true) => panic!("Cannot run both 'before-packaging' and 'before-each-package' commands at the same time."),
(false, false) => panic!("Please specify either the 'before-packaging' or 'before-each-package' command."),
}
}
/// This function is run only *once* before cargo-packager generates any package bundles.
///
/// ## Functionality
/// 1. Creates a directory for the resources to be packaged, which is currently `./dist/resources/`.
/// 2. Recursively copies the resources from the `makepad-widgets` crate to a subdirectory
/// of the directory created in step 1, which is currently `./dist/resources/makepad_widgets/`.
/// The location of the `makepad-widgets` crate is determined using `cargo-metadata`.
/// 3. Recursively copies the Moxin-specific `./resources` directory to `./dist/resources/moxin/`.
/// 4. (If macOS) Downloads WasmEdge and sets up the plugins into the `./wasmedge/` directory.
fn before_packaging(host_os: &str) -> std::io::Result<()> {
let cwd = std::env::current_dir()?;
let dist_resources_dir = cwd.join("dist").join("resources");
fs::create_dir_all(&dist_resources_dir)?;
let moxin_resources_dest = dist_resources_dir.join("moxin").join("resources");
let moxin_resources_src = cwd.join("resources");
let makepad_widgets_resources_dest = dist_resources_dir.join("makepad_widgets").join("resources");
let makepad_widgets_resources_src = {
let cargo_metadata = MetadataCommand::new()
.exec()
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
let makepad_widgets_package = cargo_metadata
.packages
.iter()
.find(|package| package.name == "makepad-widgets")
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::NotFound, "makepad-widgets package not found"))?;
makepad_widgets_package.manifest_path
.parent()
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::NotFound, "makepad-widgets package manifest path not found"))?
.join("resources")
};
/// Copy files from source to destination recursively.
fn copy_recursively(source: impl AsRef<Path>, destination: impl AsRef<Path>) -> std::io::Result<()> {
fs::create_dir_all(&destination)?;
for entry in fs::read_dir(source)? {
let entry = entry?;
let filetype = entry.file_type()?;
if filetype.is_dir() {
copy_recursively(entry.path(), destination.as_ref().join(entry.file_name()))?;
} else {
fs::copy(entry.path(), destination.as_ref().join(entry.file_name()))?;
}
}
Ok(())
}
println!("Copying makepad-widgets resources...\n --> From: {}\n to: {}", makepad_widgets_resources_src.as_std_path().display(), makepad_widgets_resources_dest.display());
copy_recursively(&makepad_widgets_resources_src, &makepad_widgets_resources_dest)?;
println!(" --> Done!");
println!("Copying moxin resources...\n --> From {}\n to: {}", moxin_resources_src.display(), moxin_resources_dest.display());
copy_recursively(&moxin_resources_src, &moxin_resources_dest)?;
println!(" --> Done!");
if host_os == "macos" {
download_wasmedge_macos("0.14.0")?;
}
Ok(())
}
/// Downloads WasmEdge and extracts it to the `./wasmedge/` directory.
///
/// This function effectively runs the following shell commands:
/// ```sh
/// mkdir -p ./wasmedge \
/// && curl -sfL --show-error https://github.com/WasmEdge/WasmEdge/releases/download/0.14.0/WasmEdge-0.14.0-darwin_arm64.tar.gz | bsdtar -xf- -C ./wasmedge \
/// && mkdir -p ./wasmedge/WasmEdge-0.14.0-Darwin/plugin \
/// && curl -sf --location --progress-bar --show-error https://github.com/WasmEdge/WasmEdge/releases/download/0.14.0/WasmEdge-plugin-wasi_nn-ggml-0.14.0-darwin_arm64.tar.gz | bsdtar -xf- -C ./wasmedge/WasmEdge-0.14.0-Darwin/plugin; \
/// ```
fn download_wasmedge_macos(version: &str) -> std::io::Result<()> {
// Command 1: Create the destination directory.
let dest_dir = std::env::current_dir()?.join("wasmedge");
fs::create_dir_all(&dest_dir)?;
// Command 2: Download and extract WasmEdge.
{
println!("Downloading wasmedge v{version} to: {}", dest_dir.display());
let curl_script_cmd = Command::new("curl")
.arg("-sSfL")
.arg("https://github.com/WasmEdge/WasmEdge/releases/download/0.14.0/WasmEdge-0.14.0-darwin_arm64.tar.gz")
.stdout(Stdio::piped())
.spawn()?;
let bsdtar_cmd = Command::new("bsdtar")
.arg("-xf-")
.arg("-C")
.arg(&dest_dir)
.stdin(Stdio::from(curl_script_cmd.stdout.expect("failed to pipe curl stdout into bsdtar stdin")))
.spawn()?;
let output = bsdtar_cmd.wait_with_output()?;
if !output.status.success() {
eprintln!("Failed to install WasmEdge: {}
------------------------- stderr: -------------------------
{:?}",
output.status,
String::from_utf8_lossy(&output.stderr),
);
return Err(std::io::Error::new(std::io::ErrorKind::Other, "The wasmedge install_v2.sh script failed."));
}
}
// Command 3: Create the plugin destination directory.
let plugin_dest_dir = dest_dir.join("WasmEdge-0.14.0-Darwin").join("plugin");
fs::create_dir_all(&plugin_dest_dir)?;
// Command 4: Download and extract the Wasi-NN plugin.
{
println!("Downloading wasmedge v{version} WASI-NN plugin to: {}", plugin_dest_dir.display());
let curl_script_cmd = Command::new("curl")
.arg("-sSfL")
.arg("https://github.com/WasmEdge/WasmEdge/releases/download/0.14.0/WasmEdge-plugin-wasi_nn-ggml-0.14.0-darwin_arm64.tar.gz")
.stdout(Stdio::piped())
.spawn()?;
let bsdtar_cmd = Command::new("bsdtar")
.arg("-xf-")
.arg("-C")
.arg(&plugin_dest_dir)
.stdin(Stdio::from(curl_script_cmd.stdout.expect("failed to pipe curl stdout into bsdtar stdin")))
.spawn()?;
let output = bsdtar_cmd.wait_with_output()?;
if !output.status.success() {
eprintln!("Failed to install WasmEdge Wasi-NN plugin: {}
------------------------- stderr: -------------------------
{:?}",
output.status,
String::from_utf8_lossy(&output.stderr),
);
return Err(std::io::Error::new(std::io::ErrorKind::Other, "The wasmedge install_v2.sh script failed."));
}
}
Ok(())
}
/// The function that is run by cargo-packager's `before-each-package-command`.
///
/// It's just a simple wrapper that invokes the function for each specific package format.
fn before_each_package(host_os: &str) -> std::io::Result<()> {
// The `CARGO_PACKAGER_FORMAT` environment variable is required.
let format = std::env::var("CARGO_PACKAGER_FORMAT")
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
let package_format = format.as_str();
println!("Running before-each-package-command for {package_format:?}");
match package_format {
"app" | "dmg" => before_each_package_macos(package_format, host_os),
"deb" => before_each_package_deb(package_format, host_os),
"appimage" => before_each_package_appimage(package_format, host_os),
"pacman" => before_each_package_pacman(package_format, host_os),
"nsis" => before_each_package_windows(package_format, host_os),
_other => return Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!("Unknown/unsupported package format {_other:?}"),
)),
}
}
/// Runs the macOS-specific build commands for "app" and "dmg" package formats.
///
/// This function effectively runs the following shell commands:
/// ```sh
/// MAKEPAD_PACKAGE_DIR=../Resources cargo build --workspace --release --features macos_bundle \
/// && install_name_tool -add_rpath "@executable_path/../Frameworks" ./target/release/_moxin_app;
/// ```
fn before_each_package_macos(package_format: &str, host_os: &str) -> std::io::Result<()> {
assert!(host_os == "macos", "'app' and 'dmg' packages can only be created on macOS.");
cargo_build(
package_format,
host_os,
&["--features", "macos_bundle"],
)?;
// Use `install_name_tool` to add the `@executable_path` rpath to the binary.
let install_name_tool_cmd = Command::new("install_name_tool")
.arg("-add_rpath")
.arg("@executable_path/../Frameworks")
.arg("./target/release/_moxin_app")
.spawn()?;
let output = install_name_tool_cmd.wait_with_output()?;
if !output.status.success() {
eprintln!("Failed to run install_name_tool command: {}
------------------------- stderr: -------------------------
{:?}",
output.status,
String::from_utf8_lossy(&output.stderr),
);
return Err(std::io::Error::new(std::io::ErrorKind::Other, "Failed to run install_name_tool command for macOS"));
}
Ok(())
}
/// Runs the Linux-specific build commands for AppImage packages.
fn before_each_package_appimage(package_format: &str, host_os: &str) -> std::io::Result<()> {
assert!(host_os == "linux", "AppImage packages can only be created on Linux.");
cargo_build(
package_format,
host_os,
std::iter::empty::<&str>(),
)?;
strip_unneeded_linux_binaries(host_os)?;
Ok(())
}
/// Runs the Linux-specific build commands for Debian `.deb` packages.
///
/// This function effectively runs the following shell commands:
/// ```sh
/// for path in $(ldd target/release/_moxin_app | awk '{print $3}'); do \
/// basename "$/path" ; \
/// done \
/// | xargs dpkg -S 2> /dev/null | awk '{print $1}' | awk -F ':' '{print $1}' | sort | uniq > ./dist/depends_deb.txt; \
/// echo "curl" >> ./dist/depends_deb.txt; \
///
fn before_each_package_deb(package_format: &str, host_os: &str) -> std::io::Result<()> {
assert!(host_os == "linux", "'deb' packages can only be created on Linux.");
cargo_build(
package_format,
host_os,
&["--features", "reqwest/native-tls-vendored"],
)?;
// Create Debian dependencies file by running `ldd` on the binary
// and then running `dpkg -S` on each unique shared libraries outputted by `ldd`.
let ldd_output = Command::new("ldd")
.arg("target/release/_moxin_app")
.output()?;
let ldd_output = if ldd_output.status.success() {
String::from_utf8_lossy(&ldd_output.stdout)
} else {
eprintln!("Failed to run ldd command: {}
------------------------- stderr: -------------------------
{:?}",
ldd_output.status,
String::from_utf8_lossy(&ldd_output.stderr),
);
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!("Failed to run ldd command on {host_os} for package format {package_format:?}")
));
};
let mut dpkgs = Vec::new();
for line_raw in ldd_output.lines() {
let line = line_raw.trim();
let lib_name_opt = line.split_whitespace()
.next()
.and_then(|path| Path::new(path)
.file_name()
.and_then(|f| f.to_str().to_owned())
);
let Some(lib_name) = lib_name_opt else { continue };
let dpkg_output = Command::new("dpkg")
.arg("-S")
.arg(lib_name)
.stderr(Stdio::null())
.output()?;
let dpkg_output = if dpkg_output.status.success() {
String::from_utf8_lossy(&dpkg_output.stdout)
} else {
// Skip shared libraries that dpkg doesn't know about, e.g., `linux-vdso.so*`
continue;
};
let Some(package_name) = dpkg_output.split(':').next() else { continue };
println!("Got dpkg dependency {package_name:?} from ldd output: {line:?}");
dpkgs.push(package_name.to_string());
}
dpkgs.sort();
dpkgs.dedup();
println!("Sorted and de-duplicated dependencies: {:#?}", dpkgs);
// `curl` is a fixed dependency for the moxin-runner binary.
dpkgs.push("curl".to_string());
std::fs::write("./dist/depends_deb.txt", dpkgs.join("\n"))?;
strip_unneeded_linux_binaries(host_os)?;
Ok(())
}
/// Runs the Linux-specific build commands for PacMan packages.
///
/// This is untested and may be incomplete, e.g., dependencies are not determined.
fn before_each_package_pacman(package_format: &str, host_os: &str) -> std::io::Result<()> {
assert!(host_os == "linux", "Pacman packages can only be created on Linux.");
cargo_build(
package_format,
host_os,
&["--features", "reqwest/native-tls-vendored"],
)?;
strip_unneeded_linux_binaries(host_os)?;
Ok(())
}
/// Runs the Windows-specific build commands for WiX (`.msi`) and NSIS (`.exe`) packages.
fn before_each_package_windows(package_format: &str, host_os: &str) -> std::io::Result<()> {
assert!(host_os == "windows", "'.exe' and '.msi' packages can only be created on Windows.");
cargo_build(
package_format,
host_os,
std::iter::empty::<&str>(),
)?;
Ok(())
}
fn cargo_build<I, S>(package_format: &str, _host_os: &str, extra_args: I) -> std::io::Result<()>
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
let cargo_build_cmd = Command::new("cargo")
.arg("build")
.arg("--workspace")
.arg("--release")
.args(extra_args)
.env("MAKEPAD_PACKAGE_DIR", makepad_package_dir_value(package_format))
.spawn()?;
let output = cargo_build_cmd.wait_with_output()?;
if !output.status.success() {
eprintln!("Failed to run cargo build command: {}
------------------------- stderr: -------------------------
{:?}",
output.status,
String::from_utf8_lossy(&output.stderr),
);
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!("Failed to run cargo build command on {_host_os} for package format {package_format:?}")
));
}
Ok(())
}
/// Strips unneeded symbols from the Linux binary, which is required for Debian `.deb` packages
/// and recommended for all other Linux package formats.
fn strip_unneeded_linux_binaries(host_os: &str) -> std::io::Result<()> {
assert!(host_os == "linux", "'strip --strip-unneeded' can only be run on Linux.");
let strip_cmd = Command::new("strip")
.arg("--strip-unneeded")
.arg("--remove-section=.comment")
.arg("--remove-section=.note")
.arg("target/release/_moxin_app")
.arg("target/release/moxin")
.spawn()?;
let output = strip_cmd.wait_with_output()?;
if !output.status.success() {
eprintln!("Failed to run strip command: {}
------------------------- stderr: -------------------------
{:?}",
output.status,
String::from_utf8_lossy(&output.stderr),
);
return Err(std::io::Error::new(std::io::ErrorKind::Other, "Failed to run strip command for Linux"));
}
Ok(())
}

View File

@ -8,7 +8,7 @@ Icon={{icon}}
Name={{name}}
Terminal=false
Type=Application
StartupWMClass={{name}}
StartupWMClass=_moxin_app
{{#if mime_type}}
MimeType={{mime_type}}
{{/if}}

View File

@ -1 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3.654 1.059 C 3.292 1.125,3.120 1.181,2.820 1.331 C 2.137 1.672,1.637 2.181,1.304 2.878 C 0.998 3.516,1.020 2.804,1.020 12.000 C 1.020 19.750,1.024 20.237,1.091 20.524 C 1.361 21.672,2.328 22.639,3.476 22.909 C 3.763 22.976,4.250 22.980,12.000 22.980 C 19.750 22.980,20.237 22.976,20.524 22.909 C 21.672 22.639,22.639 21.672,22.909 20.524 C 22.976 20.237,22.980 19.750,22.980 12.000 C 22.980 4.250,22.976 3.763,22.909 3.476 C 22.646 2.357,21.747 1.433,20.606 1.107 L 20.300 1.020 12.120 1.013 C 5.545 1.008,3.884 1.017,3.654 1.059 M7.680 12.000 L 7.680 21.000 5.842 21.000 C 4.083 21.000,3.993 20.996,3.783 20.918 C 3.528 20.822,3.251 20.572,3.113 20.315 L 3.020 20.140 3.010 12.086 C 3.003 6.680,3.013 3.981,3.040 3.879 C 3.131 3.543,3.524 3.139,3.854 3.044 C 3.923 3.024,4.813 3.006,5.830 3.004 L 7.680 3.000 7.680 12.000 M20.220 3.083 C 20.518 3.195,20.805 3.482,20.917 3.780 L 21.000 4.003 21.000 12.000 L 21.000 19.997 20.917 20.220 C 20.805 20.518,20.518 20.805,20.220 20.917 C 19.998 21.000,19.970 21.000,14.838 21.000 L 9.680 21.000 9.680 12.000 L 9.680 3.000 14.838 3.000 C 19.970 3.000,19.998 3.000,20.220 3.083 M16.115 7.735 C 16.036 7.764,15.919 7.825,15.855 7.871 C 15.631 8.034,12.287 11.422,12.215 11.560 C 12.163 11.659,12.141 11.788,12.141 12.000 C 12.140 12.255,12.156 12.327,12.247 12.480 C 12.383 12.707,15.812 16.127,15.998 16.221 C 16.074 16.259,16.249 16.300,16.387 16.311 C 16.602 16.328,16.668 16.316,16.858 16.223 C 17.358 15.978,17.575 15.387,17.338 14.912 C 17.302 14.839,16.649 14.155,15.886 13.390 L 14.500 12.000 15.886 10.610 C 16.649 9.845,17.302 9.161,17.338 9.088 C 17.573 8.619,17.349 8.002,16.863 7.775 C 16.634 7.669,16.345 7.653,16.115 7.735 " stroke="none" fill-rule="evenodd" fill="black"></path></svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -1 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M4.654 1.059 C 4.292 1.125,4.120 1.181,3.820 1.331 C 3.137 1.672,2.637 2.181,2.304 2.878 C 1.998 3.516,2.020 2.804,2.020 12.000 C 2.020 19.750,2.024 20.237,2.091 20.524 C 2.361 21.672,3.328 22.639,4.476 22.909 C 4.763 22.976,5.250 22.980,13.000 22.980 C 20.750 22.980,21.237 22.976,21.524 22.909 C 22.672 22.639,23.639 21.672,23.909 20.524 C 23.976 20.237,23.980 19.750,23.980 12.000 C 23.980 4.250,23.976 3.763,23.909 3.476 C 23.646 2.357,22.747 1.433,21.606 1.107 L 21.300 1.020 13.120 1.013 C 6.545 1.008,4.884 1.017,4.654 1.059 M7.560 12.000 L 7.560 21.000 6.282 21.000 C 5.088 21.000,4.988 20.994,4.783 20.918 C 4.528 20.822,4.251 20.572,4.113 20.315 L 4.020 20.140 4.010 12.086 C 4.003 6.680,4.013 3.981,4.040 3.879 C 4.131 3.543,4.524 3.139,4.854 3.044 C 4.923 3.024,5.560 3.006,6.270 3.004 L 7.560 3.000 7.560 12.000 M21.220 3.083 C 21.518 3.195,21.805 3.482,21.917 3.780 L 22.000 4.003 22.000 12.000 L 22.000 19.997 21.917 20.220 C 21.805 20.518,21.518 20.805,21.220 20.917 C 20.998 21.000,20.978 21.000,15.278 21.000 L 9.560 21.000 9.560 12.000 L 9.560 3.000 15.278 3.000 C 20.978 3.000,20.998 3.000,21.220 3.083 M12.670 7.735 C 12.325 7.863,12.109 8.120,12.031 8.495 C 11.991 8.684,11.995 8.750,12.056 8.948 C 12.125 9.171,12.179 9.232,13.533 10.590 L 14.938 12.000 13.533 13.410 C 12.179 14.768,12.125 14.829,12.056 15.052 C 11.994 15.252,11.991 15.315,12.032 15.512 C 12.092 15.801,12.249 16.025,12.498 16.179 C 12.671 16.286,12.729 16.300,12.997 16.299 C 13.212 16.299,13.341 16.277,13.440 16.225 C 13.632 16.124,17.059 12.704,17.193 12.480 C 17.284 12.327,17.300 12.255,17.299 12.000 C 17.299 11.788,17.277 11.659,17.225 11.560 C 17.127 11.372,13.706 7.942,13.488 7.813 C 13.277 7.687,12.895 7.651,12.670 7.735 " stroke="none" fill-rule="evenodd" fill="black"></path></svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -257,11 +257,11 @@ impl MatchEvent for App {
self.ui.redraw(cx);
}
DownloadAction::Pause(file_id) => {
self.store.downloads.pause_download_file(file_id);
self.store.downloads.pause_download_file(&file_id);
self.ui.redraw(cx);
}
DownloadAction::Cancel(file_id) => {
self.store.downloads.cancel_download_file(file_id);
self.store.downloads.cancel_download_file(&file_id);
self.ui.redraw(cx);
}
_ => {}

View File

@ -1,73 +1,22 @@
use makepad_widgets::*;
use crate::data::store::Store;
use super::chat_history_card::ChatHistoryCardWidgetRefExt;
use crate::data::store::Store;
use makepad_widgets::*;
live_design! {
import makepad_widgets::base::*;
import makepad_widgets::theme_desktop_dark::*;
import crate::shared::styles::*;
import crate::shared::widgets::FadeView;
import crate::shared::widgets::MoxinButton;
import crate::shared::widgets::*;
import makepad_draw::shader::std::*;
import crate::chat::shared::ChatAgentAvatar;
import crate::chat::chat_history_card::ChatHistoryCard;
ICON_NEW_CHAT = dep("crate://self/resources/icons/new_chat.svg")
ICON_CLOSE_PANEL = dep("crate://self/resources/icons/close_left_panel.svg")
ICON_OPEN_PANEL = dep("crate://self/resources/icons/open_left_panel.svg")
ChatHistoryActions = <View> {
spacing: 10,
height: Fit
close_panel_button = <MoxinButton> {
width: Fit,
height: Fit,
icon_walk: {width: 20, height: 20},
draw_icon: {
svg_file: (ICON_CLOSE_PANEL),
fn get_color(self) -> vec4 {
return #475467;
}
}
}
open_panel_button = <MoxinButton> {
width: Fit,
height: Fit,
visible: false,
icon_walk: {width: 20, height: 20},
draw_icon: {
svg_file: (ICON_OPEN_PANEL),
fn get_color(self) -> vec4 {
return #475467;
}
}
}
new_chat_button = <MoxinButton> {
width: Fit,
height: Fit,
icon_walk: {margin: { top: -1 }, width: 21, height: 21},
draw_icon: {
svg_file: (ICON_NEW_CHAT),
fn get_color(self) -> vec4 {
return #475467;
}
}
}
}
ChatHistory = {{ChatHistory}} {
flow: Overlay
width: Fit
height: Fill
main_content = <FadeView> {
width: 300
height: Fill,
ChatHistory = {{ChatHistory}} <MoxinTogglePanel> {
open_content = {
<View> {
width: Fill,
height: Fill,
@ -90,42 +39,36 @@ live_design! {
}
}
<ChatHistoryActions> {
padding: {top: 58, left: 25, right: 25}
}
animator: {
panel = {
default: show,
show = {
redraw: true,
from: {all: Forward {duration: 0.3}}
ease: ExpDecay {d1: 0.80, d2: 0.97}
apply: {main_content = { width: 300, draw_bg: {opacity: 1.0} }}
}
hide = {
redraw: true,
from: {all: Forward {duration: 0.3}}
ease: ExpDecay {d1: 0.80, d2: 0.97}
apply: {main_content = { width: 110, draw_bg: {opacity: 0.0} }}
persistent_content = {
default = {
after = {
new_chat_button = <MoxinButton> {
width: Fit,
height: Fit,
icon_walk: {margin: { top: -1 }, width: 21, height: 21},
draw_icon: {
svg_file: (ICON_NEW_CHAT),
fn get_color(self) -> vec4 {
return #475467;
}
}
}
}
}
}
}
}
#[derive(Live, LiveHook, Widget)]
pub struct ChatHistory {
#[deref]
view: View,
#[animator]
animator: Animator,
deref: TogglePanel,
}
impl Widget for ChatHistory {
fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
self.view.handle_event(cx, event, scope);
self.deref.handle_event(cx, event, scope);
self.widget_match_event(cx, event, scope);
// TODO This is a hack to redraw the chat history and reflect the
@ -134,10 +77,6 @@ impl Widget for ChatHistory {
if let Event::Signal = event {
self.redraw(cx);
}
if self.animator_handle_event(cx, event).must_redraw() {
self.redraw(cx);
}
}
fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep {
@ -154,7 +93,7 @@ impl Widget for ChatHistory {
let chats_count = chats.saved_chats.len();
while let Some(view_item) = self.view.draw_walk(cx, scope, walk).step() {
while let Some(view_item) = self.deref.draw_walk(cx, scope, walk).step() {
if let Some(mut list) = view_item.as_portal_list().borrow_mut() {
list.set_item_range(cx, 0, chats_count);
while let Some(item_id) = list.next_visible_item(cx) {
@ -186,13 +125,13 @@ impl WidgetMatchEvent for ChatHistory {
if self.button(id!(close_panel_button)).clicked(&actions) {
self.button(id!(close_panel_button)).set_visible(false);
self.button(id!(open_panel_button)).set_visible(true);
self.animator_play(cx, id!(panel.hide));
self.deref.set_open(cx, false);
}
if self.button(id!(open_panel_button)).clicked(&actions) {
self.button(id!(open_panel_button)).set_visible(false);
self.button(id!(close_panel_button)).set_visible(true);
self.animator_play(cx, id!(panel.show));
self.deref.set_open(cx, true);
}
}
}

View File

@ -10,11 +10,7 @@ live_design! {
import makepad_widgets::theme_desktop_dark::*;
import crate::shared::styles::*;
import crate::shared::widgets::FadeView;
import crate::shared::widgets::MoxinButton;
import crate::shared::widgets::MoxinSlider;
import crate::shared::widgets::MoxinSwitch;
import crate::shared::widgets::MoxinTextInput;
import crate::shared::widgets::*;
import makepad_draw::shader::std::*;
ICON_CLOSE_PANEL = dep("crate://self/resources/icons/close_right_panel.svg")
@ -36,50 +32,8 @@ live_design! {
}
}
ChatParamsActions = <View> {
height: Fit
flow: Right
<View> {
width: Fill
height: Fit
}
close_panel_button = <MoxinButton> {
width: Fit,
height: Fit,
icon_walk: {width: 20, height: 20},
draw_icon: {
svg_file: (ICON_CLOSE_PANEL),
fn get_color(self) -> vec4 {
return #475467;
}
}
}
open_panel_button = <MoxinButton> {
width: Fit,
height: Fit,
visible: false,
icon_walk: {width: 20, height: 20},
draw_icon: {
svg_file: (ICON_OPEN_PANEL),
fn get_color(self) -> vec4 {
return #475467;
}
}
}
}
ChatParams = {{ChatParams}} {
flow: Overlay,
width: Fit,
height: Fill,
main_content = <FadeView> {
width: 300
height: Fill
ChatParams = {{ChatParams}} <MoxinTogglePanel> {
open_content = {
<View> {
width: Fill
height: Fill
@ -239,40 +193,39 @@ live_design! {
}
}
<ChatParamsActions> {
padding: {top: 58, left: 25, right: 25}
}
animator: {
panel = {
default: show,
show = {
redraw: true,
from: {all: Forward {duration: 0.3}}
ease: ExpDecay {d1: 0.80, d2: 0.97}
apply: {main_content = { width: 300, draw_bg: {opacity: 1.0} }}
persistent_content = {
default = {
before = {
width: Fill
}
hide = {
redraw: true,
from: {all: Forward {duration: 0.3}}
ease: ExpDecay {d1: 0.80, d2: 0.97}
apply: {main_content = { width: 110, draw_bg: {opacity: 0.0} }}
open = {
draw_icon: {
svg_file: (ICON_OPEN_PANEL),
}
}
close = {
draw_icon: {
svg_file: (ICON_CLOSE_PANEL),
}
}
}
}
}
}
const TOOLTIP_OFFSET: DVec2 = DVec2 { x: -320.0, y: -30.0 };
const TOOLTIP_OFFSET_BOTTOM: DVec2 = DVec2 { x: -320.0, y: -100.0 };
const TOOLTIP_OFFSET: DVec2 = DVec2 {
x: -320.0,
y: -30.0,
};
const TOOLTIP_OFFSET_BOTTOM: DVec2 = DVec2 {
x: -320.0,
y: -100.0,
};
#[derive(Live, LiveHook, Widget)]
pub struct ChatParams {
#[deref]
view: View,
#[animator]
animator: Animator,
deref: TogglePanel,
#[rust]
current_chat_id: Option<ChatID>,
@ -280,12 +233,8 @@ pub struct ChatParams {
impl Widget for ChatParams {
fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
self.view.handle_event(cx, event, scope);
self.deref.handle_event(cx, event, scope);
self.widget_match_event(cx, event, scope);
if self.animator_handle_event(cx, event).must_redraw() {
self.redraw(cx);
}
}
fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep {
@ -327,7 +276,7 @@ impl Widget for ChatParams {
self.visible = false;
}
self.view.draw_walk(cx, scope, walk)
self.deref.draw_walk(cx, scope, walk)
}
}
@ -342,13 +291,13 @@ impl WidgetMatchEvent for ChatParams {
if close.clicked(&actions) {
close.set_visible(false);
open.set_visible(true);
self.animator_play(cx, id!(panel.hide));
self.set_open(cx, false);
}
if open.clicked(&actions) {
open.set_visible(false);
close.set_visible(true);
self.animator_play(cx, id!(panel.show));
self.set_open(cx, true);
}
if let Some(chat) = store.chats.get_current_chat() {
@ -402,7 +351,7 @@ impl WidgetMatchEvent for ChatParams {
impl ChatParams {
fn handle_tooltip_actions(&mut self, cx: &mut Cx, actions: &Actions, scope: &mut Scope) {
if self.animator.animator_in_state(cx, id!(panel.hide)) {
if !self.is_open(cx) {
return;
}

View File

@ -13,6 +13,7 @@ pub enum DownloadFileAction {
#[derive(Clone, Copy, Debug)]
pub enum DownloadState {
Initializing(f64),
Downloading(f64),
Errored(f64),
Completed,
@ -36,7 +37,7 @@ impl Download {
model: model,
sender: tx,
receiver: rx,
state: DownloadState::Downloading(progress),
state: DownloadState::Initializing(progress),
notification_pending: false,
};
@ -104,12 +105,17 @@ impl Download {
}
}
pub fn is_initializing(&self) -> bool {
matches!(self.state, DownloadState::Initializing(..))
}
pub fn is_complete(&self) -> bool {
matches!(self.state, DownloadState::Completed)
}
pub fn get_progress(&self) -> f64 {
match self.state {
DownloadState::Initializing(progress) => progress,
DownloadState::Downloading(progress) => progress,
DownloadState::Errored(progress) => progress,
DownloadState::Completed => 1.0,

View File

@ -64,13 +64,18 @@ impl Downloads {
Ok(files) => {
self.pending_downloads = files;
self.pending_downloads.sort_by(|a, b| b.file.id.cmp(&a.file.id));
self.pending_downloads
.sort_by(|a, b| b.file.id.cmp(&a.file.id));
// There is a issue with the backend response where all pending
// downloads come with status `Paused` even if they are downloading.
self.pending_downloads.iter_mut().for_each(|d| {
if self.current_downloads.contains_key(&d.file.id) {
d.status = PendingDownloadsStatus::Downloading;
if let Some(current) = self.current_downloads.get(&d.file.id) {
if current.is_initializing() {
d.status = PendingDownloadsStatus::Initializing;
} else {
d.status = PendingDownloadsStatus::Downloading;
}
}
});
}
@ -88,13 +93,13 @@ impl Downloads {
.find(|d| d.file.id == file.id)
{
current_progress = pending.progress;
pending.status = PendingDownloadsStatus::Downloading;
pending.status = PendingDownloadsStatus::Initializing;
} else {
let pending_download = PendingDownload {
file: file.clone(),
model: model.clone(),
progress: 0.0,
status: PendingDownloadsStatus::Downloading,
status: PendingDownloadsStatus::Initializing,
};
self.pending_downloads.push(pending_download);
}
@ -105,7 +110,14 @@ impl Downloads {
);
}
pub fn pause_download_file(&mut self, file_id: FileID) {
pub fn pause_download_file(&mut self, file_id: &FileID) {
let Some(current_download) = self.current_downloads.get(file_id) else {
return;
};
if current_download.is_initializing() {
return;
}
let (tx, rx) = channel();
self.backend
.as_ref()
@ -116,15 +128,25 @@ impl Downloads {
if let Ok(response) = rx.recv() {
match response {
Ok(()) => {
self.current_downloads.remove(&file_id);
self.load_pending_downloads();
self.current_downloads.remove(file_id);
self.pending_downloads.iter_mut().for_each(|d| {
if d.file.id == *file_id {
d.status = PendingDownloadsStatus::Paused;
}
});
}
Err(err) => eprintln!("Error pausing download: {:?}", err),
}
};
}
pub fn cancel_download_file(&mut self, file_id: FileID) {
pub fn cancel_download_file(&mut self, file_id: &FileID) {
if let Some(current_download) = self.current_downloads.get(file_id) {
if current_download.is_initializing() {
return;
}
};
let (tx, rx) = channel();
self.backend
.as_ref()
@ -135,8 +157,8 @@ impl Downloads {
if let Ok(response) = rx.recv() {
match response {
Ok(()) => {
self.current_downloads.remove(&file_id);
self.load_pending_downloads();
self.current_downloads.remove(file_id);
self.pending_downloads.retain(|d| d.file.id != *file_id);
}
Err(err) => eprintln!("Error cancelling download: {:?}", err),
}
@ -155,7 +177,6 @@ impl Downloads {
.context("Failed to receive delete file response")?
.context("Delete file operation failed")?;
self.load_downloaded_files();
self.load_pending_downloads();
Ok(())
@ -190,22 +211,25 @@ impl Downloads {
.find(|d| d.file.id == id.to_string())
{
match download.state {
DownloadState::Initializing(_) => {
pending.status = PendingDownloadsStatus::Initializing;
}
DownloadState::Downloading(_) => {
pending.status = PendingDownloadsStatus::Downloading;
}
DownloadState::Errored(_) => {
pending.status = PendingDownloadsStatus::Error;
if download.must_show_notification() {
self.pending_notifications.push(DownloadPendingNotification::DownloadErrored(
download.file.clone()
));
self.pending_notifications.push(
DownloadPendingNotification::DownloadErrored(download.file.clone()),
);
}
}
DownloadState::Completed => {
if download.must_show_notification() {
self.pending_notifications.push(DownloadPendingNotification::DownloadedFile(
download.file.clone()
));
self.pending_notifications.push(
DownloadPendingNotification::DownloadedFile(download.file.clone()),
);
}
}
};

View File

@ -146,7 +146,7 @@ live_design! {
Actions = <View> {
width: Fill,
height: Fit,
height: 40,
flow: Right,
spacing: 12,
@ -232,6 +232,29 @@ impl Widget for DownloadItem {
let progress_bar_width = download.progress * 6.0; // 6.0 = 600px / 100%
let label = self.label(id!(progress));
match download.status {
PendingDownloadsStatus::Initializing => {
let downloading_color = vec3(0.035, 0.572, 0.314); //#099250
label.set_text(&format!("Downloading {:.1}%", download.progress));
label.apply_over(
cx,
live! { draw_text: { color: (downloading_color) }
},
);
self.view(id!(progress_bar)).apply_over(
cx,
live! {
width: (progress_bar_width)
draw_bg: { color: (downloading_color) }
},
);
self.button(id!(pause_button)).set_visible(false);
self.button(id!(play_button)).set_visible(false);
self.button(id!(retry_button)).set_visible(false);
self.button(id!(cancel_button)).set_visible(false);
}
PendingDownloadsStatus::Downloading => {
let downloading_color = vec3(0.035, 0.572, 0.314); //#099250
@ -253,6 +276,7 @@ impl Widget for DownloadItem {
self.button(id!(pause_button)).set_visible(true);
self.button(id!(play_button)).set_visible(false);
self.button(id!(retry_button)).set_visible(false);
self.button(id!(cancel_button)).set_visible(true);
}
PendingDownloadsStatus::Paused => {
let paused_color = vec3(0.4, 0.44, 0.52); //#667085
@ -275,6 +299,7 @@ impl Widget for DownloadItem {
self.button(id!(pause_button)).set_visible(false);
self.button(id!(play_button)).set_visible(true);
self.button(id!(retry_button)).set_visible(false);
self.button(id!(cancel_button)).set_visible(true);
}
PendingDownloadsStatus::Error => {
let failed_color = vec3(0.7, 0.11, 0.09); // #B42318
@ -297,6 +322,7 @@ impl Widget for DownloadItem {
self.button(id!(pause_button)).set_visible(false);
self.button(id!(play_button)).set_visible(false);
self.button(id!(retry_button)).set_visible(true);
self.button(id!(cancel_button)).set_visible(true);
}
}

View File

@ -158,7 +158,7 @@ impl Widget for Downloads {
let download_count = pending_downloads
.iter()
.filter(|d| matches!(d.status, PendingDownloadsStatus::Downloading))
.filter(|d| matches!(d.status, PendingDownloadsStatus::Downloading | PendingDownloadsStatus::Initializing))
.count();
self.label(id!(downloading_count))
.set_text(&format!("{} downloading", download_count));

View File

@ -258,11 +258,14 @@ impl Widget for ModelFilesItem {
matches!(download.status, PendingDownloadsStatus::Downloading);
let is_retry_download_visible =
matches!(download.status, PendingDownloadsStatus::Error);
let is_cancel_download_visible = !matches!(download.status, PendingDownloadsStatus::Initializing);
let status_color = match download.status {
PendingDownloadsStatus::Downloading => vec3(0.035, 0.572, 0.314), // #099250
PendingDownloadsStatus::Paused => vec3(0.4, 0.44, 0.52), // #667085
PendingDownloadsStatus::Error => vec3(0.7, 0.11, 0.09), // #B42318
PendingDownloadsStatus::Downloading | PendingDownloadsStatus::Initializing => {
vec3(0.035, 0.572, 0.314)
} // #099250
PendingDownloadsStatus::Paused => vec3(0.4, 0.44, 0.52), // #667085
PendingDownloadsStatus::Error => vec3(0.7, 0.11, 0.09), // #B42318
};
self.apply_over(
@ -295,6 +298,9 @@ impl Widget for ModelFilesItem {
pause_download_button = {
visible: (is_pause_download_visible)
}
cancel_download_button = {
visible: (is_cancel_download_visible)
}
}
start_chat_button = { visible: false }
resume_chat_button = { visible: false }

View File

@ -466,4 +466,33 @@ live_design! {
}
}
}
TogglePanelButton = <MoxinButton> {
width: Fit,
height: Fit,
icon_walk: {width: 20, height: 20},
draw_icon: {
fn get_color(self) -> vec4 {
return #475467;
}
}
}
MoxinTogglePanel = <TogglePanel> {
persistent_content = {
default = {
open = <TogglePanelButton> {
visible: false,
draw_icon: {
svg_file: (TOGGLE_PANEL_OPEN_ICON)
}
}
close = <TogglePanelButton> {
draw_icon: {
svg_file: (TOGGLE_PANEL_CLOSE_ICON)
}
}
}
}
}
}