diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 55067d98d..934ebc626 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -91,3 +91,16 @@ windows-sys = { version = "0.60.2", features = ["Win32_Storage_FileSystem"] } tauri-plugin-updater = "2" once_cell = "1.18" tauri-plugin-single-instance = { version = "2.0.0", features = ["deep-link"] } + +# Release profile optimizations for minimal binary size +[profile.release] +opt-level = "z" # Optimize for size +lto = "fat" # Aggressive Link Time Optimization +strip = "symbols" # Strip debug symbols for smaller binary +codegen-units = 1 # Reduce parallel codegen for better optimization +panic = "abort" # Don't unwind on panic, saves space +overflow-checks = false # Disable overflow checks for size +debug = false # No debug info +debug-assertions = false # No debug assertions +incremental = false # Disable incremental compilation for release +rpath = false # Don't include rpath diff --git a/src-tauri/gen/android/app/build.gradle.kts b/src-tauri/gen/android/app/build.gradle.kts index 63192db51..8d638e839 100644 --- a/src-tauri/gen/android/app/build.gradle.kts +++ b/src-tauri/gen/android/app/build.gradle.kts @@ -38,11 +38,23 @@ android { } getByName("release") { isMinifyEnabled = true + isShrinkResources = true // Remove unused resources + signingConfig = signingConfigs.getByName("release") proguardFiles( *fileTree(".") { include("**/*.pro") } .plus(getDefaultProguardFile("proguard-android-optimize.txt")) .toList().toTypedArray() ) + // Additional size optimizations + packaging { + resources.excludes.addAll(listOf( + "META-INF/LICENSE*", + "META-INF/NOTICE*", + "META-INF/*.RSA", + "META-INF/*.SF", + "META-INF/*.DSA" + )) + } } } kotlinOptions { diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index b18bceb64..fea60701d 100644 Binary files a/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png index e69de29bb..280c35150 100644 Binary files a/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png and b/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png index b18bceb64..fea60701d 100644 Binary files a/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and b/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/src-tauri/plugins/tauri-plugin-hardware/src/vendor/vulkan.rs b/src-tauri/plugins/tauri-plugin-hardware/src/vendor/vulkan.rs index d911e7013..cad21895d 100644 --- a/src-tauri/plugins/tauri-plugin-hardware/src/vendor/vulkan.rs +++ b/src-tauri/plugins/tauri-plugin-hardware/src/vendor/vulkan.rs @@ -45,6 +45,7 @@ pub fn get_vulkan_gpus(lib_path: &str) -> Vec { } } +#[cfg(not(any(target_os = "android", target_os = "ios")))] fn parse_c_string_u8(buf: &[u8]) -> String { unsafe { std::ffi::CStr::from_ptr(buf.as_ptr() as *const std::ffi::c_char) } .to_str() diff --git a/src-tauri/src/core/server/commands.rs b/src-tauri/src/core/server/commands.rs index 85450bee5..286d40cc1 100644 --- a/src-tauri/src/core/server/commands.rs +++ b/src-tauri/src/core/server/commands.rs @@ -14,12 +14,12 @@ pub async fn start_server( api_key: String, trusted_hosts: Vec, proxy_timeout: u64, -) -> Result { +) -> Result { let server_handle = state.server_handle.clone(); let plugin_state: State = app_handle.state(); let sessions = plugin_state.llama_server_process.clone(); - proxy::start_server( + let actual_port = proxy::start_server( server_handle, sessions, host, @@ -31,7 +31,7 @@ pub async fn start_server( ) .await .map_err(|e| e.to_string())?; - Ok(true) + Ok(actual_port) } #[tauri::command] diff --git a/src-tauri/src/core/server/proxy.rs b/src-tauri/src/core/server/proxy.rs index 9b33d4ba5..74961790f 100644 --- a/src-tauri/src/core/server/proxy.rs +++ b/src-tauri/src/core/server/proxy.rs @@ -632,7 +632,7 @@ pub async fn start_server( proxy_api_key: String, trusted_hosts: Vec>, proxy_timeout: u64, -) -> Result> { +) -> Result> { let mut handle_guard = server_handle.lock().await; if handle_guard.is_some() { return Err("Server is already running".into()); @@ -667,7 +667,8 @@ pub async fn start_server( }); let server = Server::bind(&addr).serve(make_svc); - log::info!("Jan API server started on http://{}", addr); + let actual_addr = server.local_addr(); + log::info!("Jan API server started on http://{}", actual_addr); let server_task = tokio::spawn(async move { if let Err(e) = server.await { @@ -678,7 +679,9 @@ pub async fn start_server( }); *handle_guard = Some(server_task); - Ok(true) + let actual_port = actual_addr.port(); + log::info!("Jan API server started successfully on port {}", actual_port); + Ok(actual_port) } pub async fn stop_server( diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 437dcf9ac..e1c1a3fca 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -14,7 +14,10 @@ use tokio::sync::Mutex; #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { + #[cfg(desktop)] let mut builder = tauri::Builder::default(); + #[cfg(mobile)] + let builder = tauri::Builder::default(); #[cfg(desktop)] { builder = builder.plugin(tauri_plugin_single_instance::init(|_app, argv, _cwd| { diff --git a/src-tauri/tauri.android.conf.json b/src-tauri/tauri.android.conf.json new file mode 100644 index 000000000..be9c703fe --- /dev/null +++ b/src-tauri/tauri.android.conf.json @@ -0,0 +1,11 @@ +{ + "build": { + "devUrl": null, + "frontendDist": "../web-app/dist" + }, + "app": { + "security": { + "capabilities": ["default"] + } + } +} \ No newline at end of file diff --git a/src-tauri/tauri.ios.conf.json b/src-tauri/tauri.ios.conf.json index fc99b5160..c5346846d 100644 --- a/src-tauri/tauri.ios.conf.json +++ b/src-tauri/tauri.ios.conf.json @@ -1,6 +1,12 @@ { + "app": { + "security": { + "capabilities": ["default"] + } + }, "bundle": { "iOS": { + "developmentTeam": "" } } } \ No newline at end of file diff --git a/web-app/src/hooks/useLocalApiServer.ts b/web-app/src/hooks/useLocalApiServer.ts index 712277425..44389272d 100644 --- a/web-app/src/hooks/useLocalApiServer.ts +++ b/web-app/src/hooks/useLocalApiServer.ts @@ -40,7 +40,8 @@ export const useLocalApiServer = create()( setEnableOnStartup: (value) => set({ enableOnStartup: value }), serverHost: '127.0.0.1', setServerHost: (value) => set({ serverHost: value }), - serverPort: 1337, + // Use port 0 (auto-assign) for mobile to avoid conflicts, 1337 for desktop + serverPort: (typeof window !== 'undefined' && (window as any).IS_ANDROID) || (typeof window !== 'undefined' && (window as any).IS_IOS) ? 0 : 1337, setServerPort: (value) => set({ serverPort: value }), apiPrefix: '/v1', setApiPrefix: (value) => set({ apiPrefix: value }), diff --git a/web-app/src/main.tsx b/web-app/src/main.tsx index 4f48ec043..7cd390f56 100644 --- a/web-app/src/main.tsx +++ b/web-app/src/main.tsx @@ -8,6 +8,47 @@ import { routeTree } from './routeTree.gen' import './index.css' import './i18n' +// Mobile-specific viewport and styling setup +const setupMobileViewport = () => { + // Check if running on mobile platform (iOS/Android via Tauri) + const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent) || + window.matchMedia('(max-width: 768px)').matches + + if (isMobile) { + // Update viewport meta tag to disable zoom + let viewportMeta = document.querySelector('meta[name="viewport"]') + if (viewportMeta) { + viewportMeta.setAttribute('content', + 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover' + ) + } + + // Add mobile-specific styles for status bar + const style = document.createElement('style') + style.textContent = ` + body { + padding-top: env(safe-area-inset-top); + padding-bottom: env(safe-area-inset-bottom); + padding-left: env(safe-area-inset-left); + padding-right: env(safe-area-inset-right); + } + + #root { + min-height: calc(100vh - env(safe-area-inset-top) - env(safe-area-inset-bottom)); + } + + /* Prevent zoom on input focus */ + input, textarea, select { + font-size: 16px !important; + } + ` + document.head.appendChild(style) + } +} + +// Initialize mobile setup +setupMobileViewport() + // Create a new router instance const router = createRouter({ routeTree }) diff --git a/web-app/src/providers/DataProvider.tsx b/web-app/src/providers/DataProvider.tsx index a734cd39f..b7d3a3581 100644 --- a/web-app/src/providers/DataProvider.tsx +++ b/web-app/src/providers/DataProvider.tsx @@ -31,6 +31,7 @@ export function DataProvider() { enableOnStartup, serverHost, serverPort, + setServerPort, apiPrefix, apiKey, trustedHosts, @@ -173,7 +174,11 @@ export function DataProvider() { proxyTimeout: proxyTimeout, }) }) - .then(() => { + .then((actualPort: number) => { + // Store the actual port that was assigned (important for mobile with port 0) + if (actualPort && actualPort !== serverPort) { + setServerPort(actualPort) + } setServerStatus('running') }) .catch((error: unknown) => { diff --git a/web-app/src/routes/settings/local-api-server.tsx b/web-app/src/routes/settings/local-api-server.tsx index 9f2b2315b..c37ac75f2 100644 --- a/web-app/src/routes/settings/local-api-server.tsx +++ b/web-app/src/routes/settings/local-api-server.tsx @@ -48,6 +48,7 @@ function LocalAPIServerContent() { setEnableOnStartup, serverHost, serverPort, + setServerPort, apiPrefix, apiKey, trustedHosts, @@ -162,7 +163,11 @@ function LocalAPIServerContent() { proxyTimeout: proxyTimeout, }) }) - .then(() => { + .then((actualPort: number) => { + // Store the actual port that was assigned (important for mobile with port 0) + if (actualPort && actualPort !== serverPort) { + setServerPort(actualPort) + } setServerStatus('running') }) .catch((error: unknown) => {