Merge pull request #6512 from menloresearch/mobile/thinning_app

Feat: Mobile App Optimization and Backend Integration
This commit is contained in:
Nghia Doan 2025-09-18 16:30:30 +07:00 committed by GitHub
commit f90398e29a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 110 additions and 9 deletions

View File

@ -91,3 +91,16 @@ windows-sys = { version = "0.60.2", features = ["Win32_Storage_FileSystem"] }
tauri-plugin-updater = "2" tauri-plugin-updater = "2"
once_cell = "1.18" once_cell = "1.18"
tauri-plugin-single-instance = { version = "2.0.0", features = ["deep-link"] } 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

View File

@ -38,11 +38,23 @@ android {
} }
getByName("release") { getByName("release") {
isMinifyEnabled = true isMinifyEnabled = true
isShrinkResources = true // Remove unused resources
signingConfig = signingConfigs.getByName("release")
proguardFiles( proguardFiles(
*fileTree(".") { include("**/*.pro") } *fileTree(".") { include("**/*.pro") }
.plus(getDefaultProguardFile("proguard-android-optimize.txt")) .plus(getDefaultProguardFile("proguard-android-optimize.txt"))
.toList().toTypedArray() .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 { kotlinOptions {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 0 B

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -45,6 +45,7 @@ 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

@ -14,12 +14,12 @@ pub async fn start_server<R: Runtime>(
api_key: String, api_key: String,
trusted_hosts: Vec<String>, trusted_hosts: Vec<String>,
proxy_timeout: u64, proxy_timeout: u64,
) -> Result<bool, String> { ) -> Result<u16, String> {
let server_handle = state.server_handle.clone(); let server_handle = state.server_handle.clone();
let plugin_state: State<LlamacppState> = app_handle.state(); let plugin_state: State<LlamacppState> = app_handle.state();
let sessions = plugin_state.llama_server_process.clone(); let sessions = plugin_state.llama_server_process.clone();
proxy::start_server( let actual_port = proxy::start_server(
server_handle, server_handle,
sessions, sessions,
host, host,
@ -31,7 +31,7 @@ pub async fn start_server<R: Runtime>(
) )
.await .await
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
Ok(true) Ok(actual_port)
} }
#[tauri::command] #[tauri::command]

View File

@ -632,7 +632,7 @@ pub async fn start_server(
proxy_api_key: String, proxy_api_key: String,
trusted_hosts: Vec<Vec<String>>, trusted_hosts: Vec<Vec<String>>,
proxy_timeout: u64, proxy_timeout: u64,
) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> { ) -> Result<u16, Box<dyn std::error::Error + Send + Sync>> {
let mut handle_guard = server_handle.lock().await; let mut handle_guard = server_handle.lock().await;
if handle_guard.is_some() { if handle_guard.is_some() {
return Err("Server is already running".into()); return Err("Server is already running".into());
@ -667,7 +667,8 @@ pub async fn start_server(
}); });
let server = Server::bind(&addr).serve(make_svc); 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 { let server_task = tokio::spawn(async move {
if let Err(e) = server.await { if let Err(e) = server.await {
@ -678,7 +679,9 @@ pub async fn start_server(
}); });
*handle_guard = Some(server_task); *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( pub async fn stop_server(

View File

@ -14,7 +14,10 @@ use tokio::sync::Mutex;
#[cfg_attr(mobile, tauri::mobile_entry_point)] #[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| {

View File

@ -0,0 +1,11 @@
{
"build": {
"devUrl": null,
"frontendDist": "../web-app/dist"
},
"app": {
"security": {
"capabilities": ["default"]
}
}
}

View File

@ -1,6 +1,12 @@
{ {
"app": {
"security": {
"capabilities": ["default"]
}
},
"bundle": { "bundle": {
"iOS": { "iOS": {
"developmentTeam": "<DEVELOPMENT_TEAM_ID>"
} }
} }
} }

View File

@ -40,7 +40,8 @@ export const useLocalApiServer = create<LocalApiServerState>()(
setEnableOnStartup: (value) => set({ enableOnStartup: value }), setEnableOnStartup: (value) => set({ enableOnStartup: value }),
serverHost: '127.0.0.1', serverHost: '127.0.0.1',
setServerHost: (value) => set({ serverHost: value }), 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 }), setServerPort: (value) => set({ serverPort: value }),
apiPrefix: '/v1', apiPrefix: '/v1',
setApiPrefix: (value) => set({ apiPrefix: value }), setApiPrefix: (value) => set({ apiPrefix: value }),

View File

@ -8,6 +8,47 @@ import { routeTree } from './routeTree.gen'
import './index.css' import './index.css'
import './i18n' 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 // Create a new router instance
const router = createRouter({ routeTree }) const router = createRouter({ routeTree })

View File

@ -31,6 +31,7 @@ export function DataProvider() {
enableOnStartup, enableOnStartup,
serverHost, serverHost,
serverPort, serverPort,
setServerPort,
apiPrefix, apiPrefix,
apiKey, apiKey,
trustedHosts, trustedHosts,
@ -173,7 +174,11 @@ export function DataProvider() {
proxyTimeout: proxyTimeout, 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') setServerStatus('running')
}) })
.catch((error: unknown) => { .catch((error: unknown) => {

View File

@ -48,6 +48,7 @@ function LocalAPIServerContent() {
setEnableOnStartup, setEnableOnStartup,
serverHost, serverHost,
serverPort, serverPort,
setServerPort,
apiPrefix, apiPrefix,
apiKey, apiKey,
trustedHosts, trustedHosts,
@ -162,7 +163,11 @@ function LocalAPIServerContent() {
proxyTimeout: proxyTimeout, 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') setServerStatus('running')
}) })
.catch((error: unknown) => { .catch((error: unknown) => {