feat: Experiment removing hardware permission

This commit is contained in:
Vanalite 2025-09-17 17:10:30 +07:00
parent b2c5063e0b
commit 814024982e
26 changed files with 281 additions and 129 deletions

View File

@ -9,24 +9,53 @@ export PATH="$HOME/.cargo/bin:$PATH"
export JAVA_HOME=/opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home export JAVA_HOME=/opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home
export PATH="/opt/homebrew/opt/openjdk@17/bin:$PATH" export PATH="/opt/homebrew/opt/openjdk@17/bin:$PATH"
export ANDROID_HOME=~/Library/Android/sdk export ANDROID_HOME="$HOME/Library/Android/sdk"
export ANDROID_NDK_ROOT=~/Library/Android/sdk/ndk/29.0.14033849 export ANDROID_NDK_ROOT="$HOME/Library/Android/sdk/ndk/29.0.14033849"
export NDK_HOME=~/Library/Android/sdk/ndk/29.0.14033849 export NDK_HOME="$HOME/Library/Android/sdk/ndk/29.0.14033849"
# Add Android tools to PATH # Add Android tools to PATH
export PATH=$PATH:$ANDROID_HOME/platform-tools:$ANDROID_HOME/tools:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/emulator:$NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64/bin export PATH=$PATH:$ANDROID_HOME/platform-tools:$ANDROID_HOME/tools:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/emulator:$NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64/bin
# Set up CC and CXX for Android compilation # Set up CC and CXX for Android compilation
export CC_aarch64_linux_android=$NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android21-clang export CC_aarch64_linux_android="$NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android21-clang"
export CXX_aarch64_linux_android=$NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android21-clang++ export CXX_aarch64_linux_android="$NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android21-clang++"
export AR_aarch64_linux_android=$NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-ar export AR_aarch64_linux_android="$NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-ar"
export RANLIB_aarch64_linux_android=$NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-ranlib export RANLIB_aarch64_linux_android="$NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-ranlib"
# Additional environment variables for Rust cross-compilation
export CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="$NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android21-clang"
# Only set global CC and AR for Android builds (when TAURI_ANDROID_BUILD is set)
if [ "$TAURI_ANDROID_BUILD" = "true" ]; then
export CC="$NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android21-clang"
export AR="$NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-ar"
echo "Global CC and AR set for Android build"
fi
# Create symlinks for Android tools if they don't exist # Create symlinks for Android tools if they don't exist
mkdir -p ~/.local/bin mkdir -p ~/.local/bin
if [ ! -f ~/.local/bin/aarch64-linux-android-ranlib ]; then if [ ! -f ~/.local/bin/aarch64-linux-android-ranlib ]; then
ln -sf $NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-ranlib ~/.local/bin/aarch64-linux-android-ranlib ln -sf $NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-ranlib ~/.local/bin/aarch64-linux-android-ranlib
fi fi
if [ ! -f ~/.local/bin/aarch64-linux-android-clang ]; then
ln -sf $NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android21-clang ~/.local/bin/aarch64-linux-android-clang
fi
if [ ! -f ~/.local/bin/aarch64-linux-android-clang++ ]; then
ln -sf $NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android21-clang++ ~/.local/bin/aarch64-linux-android-clang++
fi
# Fix the broken clang symlinks by ensuring base clang is available
if [ ! -f ~/.local/bin/clang ]; then
ln -sf $NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64/bin/clang ~/.local/bin/clang
fi
if [ ! -f ~/.local/bin/clang++ ]; then
ln -sf $NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64/bin/clang++ ~/.local/bin/clang++
fi
# Create symlinks for target-specific ar tools
if [ ! -f ~/.local/bin/aarch64-linux-android-ar ]; then
ln -sf $NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-ar ~/.local/bin/aarch64-linux-android-ar
fi
export PATH="$HOME/.local/bin:$PATH" export PATH="$HOME/.local/bin:$PATH"
echo "Android environment configured:" echo "Android environment configured:"

View File

@ -12,6 +12,8 @@
"scripts": { "scripts": {
"lint": "yarn workspace @janhq/web-app lint", "lint": "yarn workspace @janhq/web-app lint",
"dev": "yarn dev:tauri", "dev": "yarn dev:tauri",
"ios": "yarn tauri ios dev",
"android": "yarn tauri android dev",
"build": "yarn build:web && yarn build:tauri", "build": "yarn build:web && yarn build:tauri",
"test": "vitest run", "test": "vitest run",
"test:watch": "vitest", "test:watch": "vitest",
@ -24,9 +26,9 @@
"serve:web-app": "yarn workspace @janhq/web-app serve:web", "serve:web-app": "yarn workspace @janhq/web-app serve:web",
"build:serve:web-app": "yarn build:web-app && yarn serve:web-app", "build:serve:web-app": "yarn build:web-app && yarn serve:web-app",
"dev:tauri": "yarn build:icon && yarn copy:assets:tauri && cross-env IS_CLEAN=true tauri dev", "dev:tauri": "yarn build:icon && yarn copy:assets:tauri && cross-env IS_CLEAN=true tauri dev",
"dev:ios": "yarn copy:assets:mobile && RUSTC_WRAPPER= yarn tauri ios dev", "dev:ios": "yarn copy:assets:mobile && RUSTC_WRAPPER= yarn tauri ios dev --features mobile",
"dev:android": "yarn copy:assets:mobile && yarn tauri android dev", "dev:android": "yarn copy:assets:mobile && TAURI_ANDROID_BUILD=true yarn tauri android dev --features mobile",
"build:android": "yarn build && yarn copy:assets:mobile && yarn tauri android build --no-default-features --features mobile", "build:android": "yarn build && yarn copy:assets:mobile && TAURI_ANDROID_BUILD=true yarn tauri android build --no-default-features --features mobile",
"build:ios": "yarn build && yarn copy:assets:mobile && yarn tauri ios build --no-default-features --features mobile", "build:ios": "yarn build && yarn copy:assets:mobile && yarn tauri ios build --no-default-features --features mobile",
"build:ios:device": "yarn build && yarn copy:assets:mobile && yarn tauri ios build --no-default-features --features mobile --export-method debugging", "build:ios:device": "yarn build && yarn copy:assets:mobile && yarn tauri ios build --no-default-features --features mobile --export-method debugging",
"copy:assets:tauri": "cpx \"pre-install/*.tgz\" \"src-tauri/resources/pre-install/\" && cpx \"LICENSE\" \"src-tauri/resources/\"", "copy:assets:tauri": "cpx \"pre-install/*.tgz\" \"src-tauri/resources/pre-install/\" && cpx \"LICENSE\" \"src-tauri/resources/\"",

View File

@ -7,6 +7,7 @@ ENABLE_SYSTEM_TRAY_ICON = "false"
[target.aarch64-linux-android] [target.aarch64-linux-android]
linker = "aarch64-linux-android21-clang" linker = "aarch64-linux-android21-clang"
ar = "llvm-ar" ar = "llvm-ar"
rustflags = ["-C", "link-arg=-fuse-ld=lld"]
[target.armv7-linux-androideabi] [target.armv7-linux-androideabi]
linker = "armv7a-linux-androideabi21-clang" linker = "armv7a-linux-androideabi21-clang"
@ -19,5 +20,3 @@ ar = "llvm-ar"
[target.i686-linux-android] [target.i686-linux-android]
linker = "i686-linux-android21-clang" linker = "i686-linux-android21-clang"
ar = "llvm-ar" ar = "llvm-ar"

View File

@ -23,10 +23,19 @@ default = [
"tauri/tray-icon", "tauri/tray-icon",
"tauri/test", "tauri/test",
"tauri/custom-protocol", "tauri/custom-protocol",
"hardware", "desktop",
] ]
hardware = ["dep:tauri-plugin-hardware"] hardware = ["dep:tauri-plugin-hardware"]
mobile = [] deep-link = ["dep:tauri-plugin-deep-link"]
desktop = [
"deep-link",
"hardware"
]
mobile = [
"tauri/protocol-asset",
"tauri/test",
"tauri/wry",
]
test-tauri = [ test-tauri = [
"tauri/wry", "tauri/wry",
"tauri/x11", "tauri/x11",
@ -63,11 +72,11 @@ serde_json = "1.0"
serde_yaml = "0.9.34" serde_yaml = "0.9.34"
tar = "0.4" tar = "0.4"
zip = "0.6" zip = "0.6"
tauri-plugin-deep-link = { version = "2.3.4" }
tauri-plugin-dialog = "2.2.1" tauri-plugin-dialog = "2.2.1"
tauri-plugin-deep-link = { version = "2", optional = true }
tauri-plugin-hardware = { path = "./plugins/tauri-plugin-hardware", optional = true } tauri-plugin-hardware = { path = "./plugins/tauri-plugin-hardware", optional = true }
tauri-plugin-http = { version = "2", features = ["unsafe-headers"] }
tauri-plugin-llamacpp = { path = "./plugins/tauri-plugin-llamacpp" } tauri-plugin-llamacpp = { path = "./plugins/tauri-plugin-llamacpp" }
tauri-plugin-http = { version = "2", features = ["unsafe-headers"] }
tauri-plugin-log = "2.0.0-rc" tauri-plugin-log = "2.0.0-rc"
tauri-plugin-opener = "2.2.7" tauri-plugin-opener = "2.2.7"
tauri-plugin-os = "2.2.1" tauri-plugin-os = "2.2.1"
@ -97,8 +106,16 @@ windows-sys = { version = "0.60.2", features = ["Win32_Storage_FileSystem"] }
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
tauri-plugin-updater = "2" tauri-plugin-updater = "2"
once_cell = "1.18" once_cell = "1.18"
tauri-plugin-single-instance = { version = "2", features = ["deep-link"] }
tauri-plugin-single-instance = { version = "2.3.4", features = ["deep-link"] } [target.'cfg(any(target_os = "android", target_os = "ios"))'.dependencies]
tauri-plugin-dialog = { version = "2.2.1", default-features = false }
tauri-plugin-http = { version = "2", default-features = false }
tauri-plugin-log = { version = "2.0.0-rc", default-features = false }
tauri-plugin-opener = { version = "2.2.7", default-features = false }
tauri-plugin-os = { version = "2.2.1", default-features = false }
tauri-plugin-shell = { version = "2.2.0", default-features = false }
tauri-plugin-store = { version = "2", default-features = false }
# Release profile optimizations for minimal binary size # Release profile optimizations for minimal binary size
[profile.release] [profile.release]

View File

@ -19,7 +19,6 @@
"opener:default", "opener:default",
"log:default", "log:default",
"dialog:default", "dialog:default",
"deep-link:default",
"core:webview:allow-create-webview-window", "core:webview:allow-create-webview-window",
"opener:allow-open-url", "opener:allow-open-url",
{ {
@ -55,7 +54,6 @@
] ]
}, },
"store:default", "store:default",
"llamacpp:default", "llamacpp:default"
"hardware:default"
] ]
} }

View File

@ -0,0 +1,63 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "desktop",
"description": "enables the default permissions for desktop platforms",
"windows": ["main"],
"remote": {
"urls": ["http://*"]
},
"platforms": ["linux", "macOS", "windows"],
"permissions": [
"core:default",
"core:webview:allow-set-webview-zoom",
"core:window:allow-start-dragging",
"core:window:allow-set-theme",
"shell:allow-spawn",
"shell:allow-open",
"core:app:allow-set-app-theme",
"core:window:allow-set-focus",
"os:default",
"opener:default",
"log:default",
"dialog:default",
"core:webview:allow-create-webview-window",
"opener:allow-open-url",
"store:default",
"llamacpp:default",
"deep-link:default",
"hardware:default",
{
"identifier": "http:default",
"allow": [
{
"url": "https://*:*"
},
{
"url": "http://*:*"
}
],
"deny": []
},
{
"identifier": "shell:allow-execute",
"allow": []
},
{
"identifier": "opener:allow-open-url",
"description": "opens the default permissions for the core module",
"windows": ["*"],
"allow": [
{
"url": "https://*"
},
{
"url": "http://127.0.0.1:*"
},
{
"url": "http://0.0.0.0:*"
}
]
}
]
}

View File

@ -0,0 +1,58 @@
{
"$schema": "../gen/schemas/mobile-schema.json",
"identifier": "mobile",
"description": "enables the default permissions for mobile platforms",
"windows": ["main"],
"remote": {
"urls": ["http://*"]
},
"permissions": [
"core:default",
"core:webview:allow-set-webview-zoom",
"core:window:allow-start-dragging",
"core:window:allow-set-theme",
"shell:allow-spawn",
"shell:allow-open",
"core:app:allow-set-app-theme",
"core:window:allow-set-focus",
"os:default",
"opener:default",
"log:default",
"dialog:default",
"core:webview:allow-create-webview-window",
"opener:allow-open-url",
"store:default",
{
"identifier": "http:default",
"allow": [
{
"url": "https://*:*"
},
{
"url": "http://*:*"
}
],
"deny": []
},
{
"identifier": "shell:allow-execute",
"allow": []
},
{
"identifier": "opener:allow-open-url",
"description": "opens the default permissions for the core module",
"windows": ["*"],
"allow": [
{
"url": "https://*"
},
{
"url": "http://127.0.0.1:*"
},
{
"url": "http://0.0.0.0:*"
}
]
}
]
}

View File

@ -3,6 +3,7 @@
"identifier": "system-monitor-window", "identifier": "system-monitor-window",
"description": "enables permissions for the system monitor window", "description": "enables permissions for the system monitor window",
"windows": ["system-monitor-window"], "windows": ["system-monitor-window"],
"platforms": ["linux", "macOS", "windows"],
"permissions": [ "permissions": [
"core:default", "core:default",
"core:window:allow-start-dragging", "core:window:allow-start-dragging",

View File

@ -0,0 +1,19 @@
Jan
Copyright 2025 Menlo Research
This product includes software developed by Menlo Research (https://menlo.ai).
Licensed under the Apache License, Version 2.0 (the "License");
You may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Attribution is requested in user-facing documentation and materials, where appropriate.

View File

@ -45,7 +45,6 @@ pub fn get_vulkan_gpus(lib_path: &str) -> Vec<GpuInfo> {
} }
} }
#[cfg(not(any(target_os = "android", target_os = "ios")))]
fn parse_c_string_u8(buf: &[u8]) -> String { fn parse_c_string_u8(buf: &[u8]) -> String {
unsafe { std::ffi::CStr::from_ptr(buf.as_ptr() as *const std::ffi::c_char) } unsafe { std::ffi::CStr::from_ptr(buf.as_ptr() as *const std::ffi::c_char) }
.to_str() .to_str()

View File

@ -58,8 +58,8 @@ pub fn get_app_configurations<R: Runtime>(app_handle: tauri::AppHandle<R>) -> Ap
} }
#[tauri::command] #[tauri::command]
pub fn update_app_configuration( pub fn update_app_configuration<R: Runtime>(
app_handle: tauri::AppHandle, app_handle: tauri::AppHandle<R>,
configuration: AppConfiguration, configuration: AppConfiguration,
) -> Result<(), String> { ) -> Result<(), String> {
let configuration_file = get_configuration_file_path(app_handle); let configuration_file = get_configuration_file_path(app_handle);
@ -155,13 +155,13 @@ pub fn default_data_folder_path<R: Runtime>(app_handle: tauri::AppHandle<R>) ->
} }
#[tauri::command] #[tauri::command]
pub fn get_user_home_path(app: AppHandle) -> String { pub fn get_user_home_path<R: Runtime>(app: AppHandle<R>) -> String {
return get_app_configurations(app.clone()).data_folder; return get_app_configurations(app.clone()).data_folder;
} }
#[tauri::command] #[tauri::command]
pub fn change_app_data_folder( pub fn change_app_data_folder<R: Runtime>(
app_handle: tauri::AppHandle, app_handle: tauri::AppHandle<R>,
new_data_folder: String, new_data_folder: String,
) -> Result<(), String> { ) -> Result<(), String> {
// Get current data folder path // Get current data folder path

View File

@ -3,12 +3,12 @@ use super::models::DownloadItem;
use crate::core::app::commands::get_jan_data_folder_path; use crate::core::app::commands::get_jan_data_folder_path;
use crate::core::state::AppState; use crate::core::state::AppState;
use std::collections::HashMap; use std::collections::HashMap;
use tauri::State; use tauri::{Runtime, State};
use tokio_util::sync::CancellationToken; use tokio_util::sync::CancellationToken;
#[tauri::command] #[tauri::command]
pub async fn download_files( pub async fn download_files<R: Runtime>(
app: tauri::AppHandle, app: tauri::AppHandle<R>,
state: State<'_, AppState>, state: State<'_, AppState>,
items: Vec<DownloadItem>, items: Vec<DownloadItem>,
task_id: &str, task_id: &str,

View File

@ -6,7 +6,7 @@ use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
use std::collections::HashMap; use std::collections::HashMap;
use std::path::Path; use std::path::Path;
use std::time::Duration; use std::time::Duration;
use tauri::Emitter; use tauri::{Emitter, Runtime};
use tokio::fs::File; use tokio::fs::File;
use tokio::io::AsyncWriteExt; use tokio::io::AsyncWriteExt;
use tokio_util::sync::CancellationToken; use tokio_util::sync::CancellationToken;
@ -25,7 +25,7 @@ pub fn err_to_string<E: std::fmt::Display>(e: E) -> String {
async fn validate_downloaded_file( async fn validate_downloaded_file(
item: &DownloadItem, item: &DownloadItem,
save_path: &Path, save_path: &Path,
app: &tauri::AppHandle, app: &tauri::AppHandle<impl Runtime>,
cancel_token: &CancellationToken, cancel_token: &CancellationToken,
) -> Result<(), String> { ) -> Result<(), String> {
// Skip validation if no verification data is provided // Skip validation if no verification data is provided
@ -298,7 +298,7 @@ pub async fn _get_file_size(
/// Downloads multiple files in parallel with individual progress tracking /// Downloads multiple files in parallel with individual progress tracking
pub async fn _download_files_internal( pub async fn _download_files_internal(
app: tauri::AppHandle, app: tauri::AppHandle<impl Runtime>,
items: &[DownloadItem], items: &[DownloadItem],
headers: &HashMap<String, String>, headers: &HashMap<String, String>,
task_id: &str, task_id: &str,
@ -423,7 +423,7 @@ pub async fn _download_files_internal(
/// Downloads a single file without blocking other downloads /// Downloads a single file without blocking other downloads
async fn download_single_file( async fn download_single_file(
app: tauri::AppHandle, app: tauri::AppHandle<impl Runtime>,
item: &DownloadItem, item: &DownloadItem,
header_map: &HeaderMap, header_map: &HeaderMap,
save_path: &std::path::Path, save_path: &std::path::Path,

View File

@ -1,24 +1,24 @@
use std::fs; use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
use tauri::AppHandle; use tauri::{AppHandle, Runtime};
use crate::core::app::commands::get_jan_data_folder_path; use crate::core::app::commands::get_jan_data_folder_path;
use crate::core::setup; use crate::core::setup;
#[tauri::command] #[tauri::command]
pub fn get_jan_extensions_path(app_handle: tauri::AppHandle) -> PathBuf { pub fn get_jan_extensions_path<R: Runtime>(app_handle: tauri::AppHandle<R>) -> PathBuf {
get_jan_data_folder_path(app_handle).join("extensions") get_jan_data_folder_path(app_handle).join("extensions")
} }
#[tauri::command] #[tauri::command]
pub fn install_extensions(app: AppHandle) { pub fn install_extensions<R: Runtime>(app: AppHandle<R>) {
if let Err(err) = setup::install_extensions(app, true) { if let Err(err) = setup::install_extensions(app, true) {
log::error!("Failed to install extensions: {}", err); log::error!("Failed to install extensions: {}", err);
} }
} }
#[tauri::command] #[tauri::command]
pub fn get_active_extensions(app: AppHandle) -> Vec<serde_json::Value> { pub fn get_active_extensions<R: Runtime>(app: AppHandle<R>) -> Vec<serde_json::Value> {
let mut path = get_jan_extensions_path(app); let mut path = get_jan_extensions_path(app);
path.push("extensions.json"); path.push("extensions.json");
log::info!("get jan extensions, path: {:?}", path); log::info!("get jan extensions, path: {:?}", path);

View File

@ -124,7 +124,7 @@ pub fn readdir_sync<R: Runtime>(
#[tauri::command] #[tauri::command]
pub fn write_yaml( pub fn write_yaml(
app: tauri::AppHandle, app: tauri::AppHandle<impl Runtime>,
data: serde_json::Value, data: serde_json::Value,
save_path: &str, save_path: &str,
) -> Result<(), String> { ) -> Result<(), String> {
@ -145,7 +145,7 @@ pub fn write_yaml(
} }
#[tauri::command] #[tauri::command]
pub fn read_yaml(app: tauri::AppHandle, path: &str) -> Result<serde_json::Value, String> { pub fn read_yaml<R: Runtime>(app: tauri::AppHandle<R>, path: &str) -> Result<serde_json::Value, String> {
let jan_data_folder = crate::core::app::commands::get_jan_data_folder_path(app.clone()); let jan_data_folder = crate::core::app::commands::get_jan_data_folder_path(app.clone());
let path = jan_utils::normalize_path(&jan_data_folder.join(path)); let path = jan_utils::normalize_path(&jan_data_folder.join(path));
if !path.starts_with(&jan_data_folder) { if !path.starts_with(&jan_data_folder) {
@ -162,7 +162,7 @@ pub fn read_yaml(app: tauri::AppHandle, path: &str) -> Result<serde_json::Value,
} }
#[tauri::command] #[tauri::command]
pub fn decompress(app: tauri::AppHandle, path: &str, output_dir: &str) -> Result<(), String> { pub fn decompress<R: Runtime>(app: tauri::AppHandle<R>, path: &str, output_dir: &str) -> Result<(), String> {
let jan_data_folder = crate::core::app::commands::get_jan_data_folder_path(app.clone()); let jan_data_folder = crate::core::app::commands::get_jan_data_folder_path(app.clone());
let path_buf = jan_utils::normalize_path(&jan_data_folder.join(path)); let path_buf = jan_utils::normalize_path(&jan_data_folder.join(path));

View File

@ -80,7 +80,7 @@ pub async fn deactivate_mcp_server(state: State<'_, AppState>, name: String) ->
} }
#[tauri::command] #[tauri::command]
pub async fn restart_mcp_servers(app: AppHandle, state: State<'_, AppState>) -> Result<(), String> { pub async fn restart_mcp_servers<R: Runtime>(app: AppHandle<R>, state: State<'_, AppState>) -> Result<(), String> {
let servers = state.mcp_servers.clone(); let servers = state.mcp_servers.clone();
// Stop the servers // Stop the servers
stop_mcp_servers(state.mcp_servers.clone()).await?; stop_mcp_servers(state.mcp_servers.clone()).await?;
@ -119,7 +119,7 @@ pub async fn reset_mcp_restart_count(
#[tauri::command] #[tauri::command]
pub async fn get_connected_servers( pub async fn get_connected_servers(
_app: AppHandle, _app: AppHandle<impl Runtime>,
state: State<'_, AppState>, state: State<'_, AppState>,
) -> Result<Vec<String>, String> { ) -> Result<Vec<String>, String> {
let servers = state.mcp_servers.clone(); let servers = state.mcp_servers.clone();
@ -293,7 +293,7 @@ pub async fn cancel_tool_call(
} }
#[tauri::command] #[tauri::command]
pub async fn get_mcp_configs(app: AppHandle) -> Result<String, String> { pub async fn get_mcp_configs<R: Runtime>(app: AppHandle<R>) -> Result<String, String> {
let mut path = get_jan_data_folder_path(app); let mut path = get_jan_data_folder_path(app);
path.push("mcp_config.json"); path.push("mcp_config.json");
@ -308,7 +308,7 @@ pub async fn get_mcp_configs(app: AppHandle) -> Result<String, String> {
} }
#[tauri::command] #[tauri::command]
pub async fn save_mcp_configs(app: AppHandle, configs: String) -> Result<(), String> { pub async fn save_mcp_configs<R: Runtime>(app: AppHandle<R>, configs: String) -> Result<(), String> {
let mut path = get_jan_data_folder_path(app); let mut path = get_jan_data_folder_path(app);
path.push("mcp_config.json"); path.push("mcp_config.json");
log::info!("save mcp configs, path: {:?}", path); log::info!("save mcp configs, path: {:?}", path);

View File

@ -767,7 +767,7 @@ pub async fn start_server(
}); });
*handle_guard = Some(server_task); *handle_guard = Some(server_task);
let actual_port = actual_addr.port(); let actual_port = addr.port();
log::info!("Jan API server started successfully on port {}", actual_port); log::info!("Jan API server started successfully on port {}", actual_port);
Ok(actual_port) Ok(actual_port)
} }

View File

@ -6,7 +6,7 @@ use std::{
}; };
use tar::Archive; use tar::Archive;
use tauri::{ use tauri::{
App, Emitter, Manager, App, Emitter, Manager, Runtime,
}; };
#[cfg(desktop)] #[cfg(desktop)]
@ -25,7 +25,7 @@ use super::{
mcp::helpers::run_mcp_commands, state::AppState, mcp::helpers::run_mcp_commands, state::AppState,
}; };
pub fn install_extensions(app: tauri::AppHandle, force: bool) -> Result<(), String> { pub fn install_extensions<R: Runtime>(app: tauri::AppHandle<R>, force: bool) -> Result<(), String> {
let mut store_path = get_jan_data_folder_path(app.clone()); let mut store_path = get_jan_data_folder_path(app.clone());
store_path.push("store.json"); store_path.push("store.json");
let store = app.store(store_path).expect("Store not initialized"); let store = app.store(store_path).expect("Store not initialized");
@ -201,10 +201,10 @@ pub fn extract_extension_manifest<R: Read>(
Ok(None) Ok(None)
} }
pub fn setup_mcp(app: &App) { pub fn setup_mcp<R: Runtime>(app: &App<R>) {
let state = app.state::<AppState>(); let state = app.state::<AppState>();
let servers = state.mcp_servers.clone(); let servers = state.mcp_servers.clone();
let app_handle: tauri::AppHandle = app.handle().clone(); let app_handle = app.handle().clone();
tauri::async_runtime::spawn(async move { tauri::async_runtime::spawn(async move {
if let Err(e) = run_mcp_commands(&app_handle, servers).await { if let Err(e) = run_mcp_commands(&app_handle, servers).await {
log::error!("Failed to run mcp commands: {}", e); log::error!("Failed to run mcp commands: {}", e);

View File

@ -1,6 +1,6 @@
use std::fs; use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
use tauri::{AppHandle, Manager, State}; use tauri::{AppHandle, Manager, Runtime, State};
use tauri_plugin_llamacpp::cleanup_llama_processes; use tauri_plugin_llamacpp::cleanup_llama_processes;
use crate::core::app::commands::{ use crate::core::app::commands::{
@ -11,7 +11,7 @@ use crate::core::mcp::helpers::clean_up_mcp_servers;
use crate::core::state::AppState; use crate::core::state::AppState;
#[tauri::command] #[tauri::command]
pub fn factory_reset(app_handle: tauri::AppHandle, state: State<'_, AppState>) { pub fn factory_reset<R: Runtime>(app_handle: tauri::AppHandle<R>, state: State<'_, AppState>) {
// close window (not available on mobile platforms) // close window (not available on mobile platforms)
#[cfg(not(any(target_os = "ios", target_os = "android")))] #[cfg(not(any(target_os = "ios", target_os = "android")))]
{ {
@ -49,12 +49,12 @@ pub fn factory_reset(app_handle: tauri::AppHandle, state: State<'_, AppState>) {
} }
#[tauri::command] #[tauri::command]
pub fn relaunch(app: AppHandle) { pub fn relaunch<R: Runtime>(app: AppHandle<R>) {
app.restart() app.restart()
} }
#[tauri::command] #[tauri::command]
pub fn open_app_directory(app: AppHandle) { pub fn open_app_directory<R: Runtime>(app: AppHandle<R>) {
let app_path = app.path().app_data_dir().unwrap(); let app_path = app.path().app_data_dir().unwrap();
if cfg!(target_os = "windows") { if cfg!(target_os = "windows") {
std::process::Command::new("explorer") std::process::Command::new("explorer")
@ -96,7 +96,7 @@ pub fn open_file_explorer(path: String) {
} }
#[tauri::command] #[tauri::command]
pub async fn read_logs(app: AppHandle) -> Result<String, String> { pub async fn read_logs<R: Runtime>(app: AppHandle<R>) -> Result<String, String> {
let log_path = get_jan_data_folder_path(app).join("logs").join("app.log"); let log_path = get_jan_data_folder_path(app).join("logs").join("app.log");
if log_path.exists() { if log_path.exists() {
let content = fs::read_to_string(log_path).map_err(|e| e.to_string())?; let content = fs::read_to_string(log_path).map_err(|e| e.to_string())?;

View File

@ -8,54 +8,23 @@ use core::{
}; };
use jan_utils::generate_app_token; use jan_utils::generate_app_token;
use std::{collections::HashMap, sync::Arc}; use std::{collections::HashMap, sync::Arc};
#[cfg(not(any(target_os = "ios", target_os = "android")))]
use tauri_plugin_deep_link::DeepLinkExt;
use tauri::{Emitter, Manager, RunEvent}; use tauri::{Emitter, Manager, RunEvent};
use tauri_plugin_llamacpp::cleanup_llama_processes; use tauri_plugin_llamacpp::cleanup_llama_processes;
use tokio::sync::Mutex; use tokio::sync::Mutex;
#[cfg(desktop)] #[cfg_attr(all(mobile, any(target_os = "android", target_os = "ios")), tauri::mobile_entry_point)]
use crate::core::setup::setup_tray;
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() { pub fn run() {
#[cfg(desktop)]
let mut builder = tauri::Builder::default(); let mut builder = tauri::Builder::default();
#[cfg(mobile)]
let builder = tauri::Builder::default();
#[cfg(desktop)] #[cfg(desktop)]
{ {
builder = builder.plugin(tauri_plugin_single_instance::init(|_app, argv, _cwd| { builder = builder.plugin(tauri_plugin_single_instance::init(|_app, argv, _cwd| {
println!("a new app instance was opened with {argv:?} and the deep link event was already triggered"); println!("a new app instance was opened with {argv:?} and the deep link event was already triggered");
// when defining deep link schemes at runtime, you must also check `argv` here // when defining deep link schemes at runtime, you must also check `argv` here
let arg = argv.iter().find(|arg| arg.starts_with("jan://"));
if let Some(deep_link) = arg {
println!("deep link: {deep_link}");
// handle the deep link, e.g., emit an event to the webview
_app.app_handle().emit("deep-link", deep_link).unwrap();
if let Some(window) = _app.app_handle().get_webview_window("main") {
let _ = window.set_focus();
}
}
})); }));
} }
#[cfg(feature = "hardware")] let mut app_builder = builder
let app = builder
.plugin(tauri_plugin_os::init()) .plugin(tauri_plugin_os::init())
.plugin(tauri_plugin_deep_link::init())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_http::init())
.plugin(tauri_plugin_store::Builder::new().build())
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_llamacpp::init())
.plugin(tauri_plugin_hardware::init());
#[cfg(not(feature = "hardware"))]
let app = builder
.plugin(tauri_plugin_os::init())
.plugin(tauri_plugin_deep_link::init())
.plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_http::init()) .plugin(tauri_plugin_http::init())
@ -63,7 +32,17 @@ pub fn run() {
.plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_llamacpp::init()); .plugin(tauri_plugin_llamacpp::init());
let app = app #[cfg(feature = "deep-link")]
{
app_builder = app_builder.plugin(tauri_plugin_deep_link::init());
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
{
app_builder = app_builder.plugin(tauri_plugin_hardware::init());
}
let app = app_builder
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
// FS commands - Deperecate soon // FS commands - Deperecate soon
core::filesystem::commands::join_path, core::filesystem::commands::join_path,
@ -138,24 +117,6 @@ pub fn run() {
server_handle: Arc::new(Mutex::new(None)), server_handle: Arc::new(Mutex::new(None)),
tool_call_cancellations: Arc::new(Mutex::new(HashMap::new())), tool_call_cancellations: Arc::new(Mutex::new(HashMap::new())),
}) })
.on_window_event(|window, event| match event {
tauri::WindowEvent::CloseRequested { api, .. } => {
if option_env!("ENABLE_SYSTEM_TRAY_ICON").unwrap_or("false") == "true" {
#[cfg(target_os = "macos")]
window
.app_handle()
.set_activation_policy(tauri::ActivationPolicy::Accessory)
.unwrap();
#[cfg(not(any(target_os = "ios", target_os = "android")))]
window.hide().unwrap();
#[cfg(any(target_os = "ios", target_os = "android"))]
let _ = window; // Use window to avoid unused variable warning
api.prevent_close();
}
}
_ => {}
})
.setup(|app| { .setup(|app| {
app.handle().plugin( app.handle().plugin(
tauri_plugin_log::Builder::default() tauri_plugin_log::Builder::default()
@ -177,15 +138,11 @@ pub fn run() {
log::error!("Failed to install extensions: {}", e); log::error!("Failed to install extensions: {}", e);
} }
#[cfg(desktop)] #[cfg(all(feature = "deep-link", any(windows, target_os = "linux")))]
if option_env!("ENABLE_SYSTEM_TRAY_ICON").unwrap_or("false") == "true" {
log::info!("Enabling system tray icon");
let _ = setup_tray(app);
}
#[cfg(all(not(any(target_os = "ios", target_os = "android")), any(windows, target_os = "linux")))]
{ {
app.deep_link().register_all()?; use tauri_plugin_deep_link::DeepLinkExt;
// Register the deep-link scheme programmatically
app.deep_link().register("jan")?;
} }
setup_mcp(app); setup_mcp(app);
Ok(()) Ok(())
@ -199,13 +156,6 @@ pub fn run() {
// This is called when the app is actually exiting (e.g., macOS dock quit) // This is called when the app is actually exiting (e.g., macOS dock quit)
// We can't prevent this, so run cleanup quickly // We can't prevent this, so run cleanup quickly
let app_handle = app.clone(); let app_handle = app.clone();
// Hide window immediately
if let Some(window) = app_handle.get_webview_window("main") {
#[cfg(not(any(target_os = "ios", target_os = "android")))]
let _ = window.hide();
#[cfg(any(target_os = "ios", target_os = "android"))]
let _ = window; // Use window to avoid unused variable warning
}
tokio::task::block_in_place(|| { tokio::task::block_in_place(|| {
tauri::async_runtime::block_on(async { tauri::async_runtime::block_on(async {
// Hide window immediately (not available on mobile platforms) // Hide window immediately (not available on mobile platforms)

View File

@ -1,13 +1,15 @@
{ {
"identifier": "jan.ai.app",
"build": { "build": {
"devUrl": null, "devUrl": null,
"frontendDist": "../web-app/dist" "frontendDist": "../web-app/dist"
}, },
"app": { "app": {
"security": { "security": {
"capabilities": ["default"] "capabilities": ["mobile"]
} }
}, },
"plugins": {},
"bundle": { "bundle": {
"resources": ["resources/LICENSE", "resources/pre-install"], "resources": ["resources/LICENSE", "resources/pre-install"],
"externalBin": [] "externalBin": []

View File

@ -72,8 +72,7 @@
"windows": { "windows": {
"installMode": "passive" "installMode": "passive"
} }
}, }
"deep-link": { "schemes": ["jan"] }
}, },
"bundle": { "bundle": {
"active": true, "active": true,

View File

@ -3,11 +3,13 @@
"devUrl": null, "devUrl": null,
"frontendDist": "../web-app/dist" "frontendDist": "../web-app/dist"
}, },
"identifier": "jan.ai.app.ios",
"app": { "app": {
"security": { "security": {
"capabilities": ["default"] "capabilities": ["mobile"]
} }
}, },
"plugins": {},
"bundle": { "bundle": {
"active": true, "active": true,
"iOS": { "iOS": {

View File

@ -1,8 +1,12 @@
{ {
"app": {
"security": {
"capabilities": ["default", "system-monitor-window"]
}
},
"bundle": { "bundle": {
"targets": ["deb", "appimage"], "targets": ["deb", "appimage"],
"resources": ["resources/pre-install/**/*", "resources/LICENSE"], "resources": ["resources/LICENSE"],
"externalBin": ["resources/bin/uv"],
"linux": { "linux": {
"appimage": { "appimage": {
"bundleMediaFramework": false, "bundleMediaFramework": false,

View File

@ -1,7 +1,11 @@
{ {
"app": {
"security": {
"capabilities": ["default", "system-monitor-window"]
}
},
"bundle": { "bundle": {
"targets": ["app", "dmg"], "targets": ["app", "dmg"],
"resources": ["resources/pre-install/**/*", "resources/LICENSE"], "resources": ["resources/LICENSE"]
"externalBin": ["resources/bin/bun", "resources/bin/uv"]
} }
} }

View File

@ -1,4 +1,10 @@
{ {
"app": {
"security": {
"capabilities": ["default"]
}
},
"bundle": { "bundle": {
"targets": ["nsis"], "targets": ["nsis"],
"resources": ["resources/pre-install/**/*", "resources/lib/vulkan-1.dll", "resources/lib/vc_redist.x64.exe", "resources/LICENSE"], "resources": ["resources/pre-install/**/*", "resources/lib/vulkan-1.dll", "resources/lib/vc_redist.x64.exe", "resources/LICENSE"],