mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
feat: add platform detection and support for mobile in ModelDownloadProgress and SkillsGrid components
- Integrated the @tauri-apps/plugin-os to detect mobile platforms (Android and iOS) in both ModelDownloadProgress and SkillsGrid components. - Updated rendering logic to display mobile-specific messages, indicating that certain features are available only on desktop. - Enhanced package.json and yarn.lock to include the new plugin dependency for platform detection. - Adjusted capabilities configuration to support the new plugin across desktop and mobile platforms.
This commit is contained in:
@@ -201,11 +201,8 @@ jobs:
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
|
||||
|
||||
- name: Install OpenMP (macOS only)
|
||||
if: matrix.settings.platform == 'macos-latest'
|
||||
run: |
|
||||
brew install libomp
|
||||
echo "OpenMP installed for llama-cpp-sys build"
|
||||
# OpenMP is now disabled via Cargo.toml (default-features = false for llama-cpp-2)
|
||||
# This avoids cross-compilation issues where Homebrew ARM64 OpenMP can't be used for x86_64 builds
|
||||
|
||||
- name: Cache Cargo registry
|
||||
uses: actions/cache@v4
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/plugin-deep-link": "^2",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"@tauri-apps/plugin-os": "^2.3.2",
|
||||
"@tauri-apps/plugin-shell": "~2",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"buffer": "^6.0.3",
|
||||
|
||||
Generated
+95
-11
@@ -23,7 +23,6 @@ dependencies = [
|
||||
"llama-cpp-2",
|
||||
"log",
|
||||
"once_cell",
|
||||
"openssl",
|
||||
"parking_lot",
|
||||
"rand 0.8.5",
|
||||
"reqwest",
|
||||
@@ -38,6 +37,7 @@ dependencies = [
|
||||
"tauri-plugin-deep-link",
|
||||
"tauri-plugin-notification",
|
||||
"tauri-plugin-opener",
|
||||
"tauri-plugin-os",
|
||||
"tdlib-rs",
|
||||
"tokio",
|
||||
"tokio-tungstenite 0.24.0",
|
||||
@@ -1917,6 +1917,16 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gethostname"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8"
|
||||
dependencies = [
|
||||
"rustix 1.1.3",
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.1.16"
|
||||
@@ -3094,6 +3104,18 @@ version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.30.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nodrop"
|
||||
version = "0.1.14"
|
||||
@@ -3268,6 +3290,16 @@ dependencies = [
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-location"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009"
|
||||
dependencies = [
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-text"
|
||||
version = "0.3.2"
|
||||
@@ -3372,8 +3404,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2-cloud-kit",
|
||||
"objc2-core-data",
|
||||
"objc2-core-foundation",
|
||||
"objc2-core-graphics",
|
||||
"objc2-core-image",
|
||||
"objc2-core-location",
|
||||
"objc2-core-text",
|
||||
"objc2-foundation",
|
||||
"objc2-quartz-core",
|
||||
"objc2-user-notifications",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-user-notifications"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e"
|
||||
dependencies = [
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
@@ -3455,15 +3506,6 @@ version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-src"
|
||||
version = "300.5.5+3.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f1787d533e03597a7934fd0a765f0d28e94ecc5fb7789f8053b1e699a56f709"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.111"
|
||||
@@ -3472,7 +3514,6 @@ checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"openssl-src",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
@@ -3503,6 +3544,22 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "os_info"
|
||||
version = "3.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4022a17595a00d6a369236fdae483f0de7f0a339960a53118b818238e132224"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"log",
|
||||
"nix",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
"objc2-ui-kit",
|
||||
"serde",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "outref"
|
||||
version = "0.1.0"
|
||||
@@ -5112,6 +5169,15 @@ dependencies = [
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sys-locale"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration"
|
||||
version = "0.6.1"
|
||||
@@ -5416,6 +5482,24 @@ dependencies = [
|
||||
"zbus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-os"
|
||||
version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8f08346c8deb39e96f86973da0e2d76cbb933d7ac9b750f6dc4daf955a6f997"
|
||||
dependencies = [
|
||||
"gethostname",
|
||||
"log",
|
||||
"os_info",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serialize-to-javascript",
|
||||
"sys-locale",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime"
|
||||
version = "2.9.2"
|
||||
|
||||
+12
-8
@@ -22,6 +22,7 @@ tauri-build = { version = "2", features = [] }
|
||||
tauri = { version = "2", features = ["tray-icon", "macos-private-api"] }
|
||||
tauri-plugin-opener = "2"
|
||||
tauri-plugin-deep-link = "2.0.0"
|
||||
tauri-plugin-os = "2"
|
||||
# tauri-plugin-autostart moved to desktop-only target section
|
||||
# tauri-plugin-notification moved to desktop-only target section
|
||||
# Serialization
|
||||
@@ -74,12 +75,15 @@ chrono = { version = "0.4", features = ["serde"] }
|
||||
cron = "0.12"
|
||||
|
||||
# Persistent Socket.io client (Rust-native, survives app backgrounding)
|
||||
rust_socketio = { version = "0.6", features = ["async"] }
|
||||
# Note: Using default native-tls on desktop, but we'll handle Android separately
|
||||
futures-util = "0.3"
|
||||
|
||||
# Desktop/iOS use native-tls for Socket.io
|
||||
[target.'cfg(not(target_os = "android"))'.dependencies]
|
||||
rust_socketio = { version = "0.6", features = ["async"] }
|
||||
|
||||
# Android uses a simpler approach - no persistent Socket.io (use web socket from frontend)
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
# Vendor OpenSSL for Android (rust_socketio requires native-tls which needs OpenSSL)
|
||||
openssl = { version = "0.10", features = ["vendored"] }
|
||||
# Android logging (routes Rust `log` crate to logcat)
|
||||
android_logger = "0.14"
|
||||
|
||||
@@ -92,16 +96,16 @@ tauri-plugin-notification = "2"
|
||||
deno_core = "0.314"
|
||||
# TDLib Rust bindings (desktop only - downloads prebuilt TDLib library)
|
||||
tdlib-rs = { version = "1.2", features = ["download-tdlib"] }
|
||||
# Local LLM inference - desktop only (llama.cpp requires native C++ compilation)
|
||||
# Disable default features (including openmp) to avoid cross-compilation issues
|
||||
# on macOS where Homebrew installs ARM64-only OpenMP libraries
|
||||
llama-cpp-2 = { version = "0.1", default-features = false }
|
||||
encoding_rs = "0.8"
|
||||
|
||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.build-dependencies]
|
||||
# TDLib build configuration
|
||||
tdlib-rs = { version = "1.2", features = ["download-tdlib"] }
|
||||
|
||||
# Local LLM inference - available on desktop and Android (not iOS)
|
||||
[target.'cfg(not(target_os = "ios"))'.dependencies]
|
||||
llama-cpp-2 = "0.1"
|
||||
encoding_rs = "0.8"
|
||||
|
||||
[features]
|
||||
# This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!!
|
||||
custom-protocol = ["tauri/custom-protocol"]
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"core:default",
|
||||
"opener:default",
|
||||
"deep-link:default",
|
||||
"os:default",
|
||||
"core:tray:default",
|
||||
"autostart:default",
|
||||
"autostart:allow-enable",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"core:default",
|
||||
"opener:default",
|
||||
"deep-link:default",
|
||||
"os:default",
|
||||
"shell:default",
|
||||
"shell:allow-spawn",
|
||||
"shell:allow-open"
|
||||
|
||||
@@ -51,14 +51,16 @@ fn default_top_p() -> f32 {
|
||||
}
|
||||
|
||||
/// Check if the local model API is available on this platform.
|
||||
/// Note: Currently only available on desktop (Windows, macOS, Linux).
|
||||
/// Android/iOS support requires additional llama.cpp NDK configuration.
|
||||
#[tauri::command]
|
||||
pub fn model_is_available() -> bool {
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
{
|
||||
false
|
||||
}
|
||||
@@ -67,7 +69,7 @@ pub fn model_is_available() -> bool {
|
||||
/// Get the current model status.
|
||||
#[tauri::command]
|
||||
pub fn model_get_status() -> ModelStatusResponse {
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
let status = crate::services::llama::LLAMA_MANAGER.get_status();
|
||||
ModelStatusResponse {
|
||||
@@ -80,14 +82,14 @@ pub fn model_get_status() -> ModelStatusResponse {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
{
|
||||
ModelStatusResponse {
|
||||
available: false,
|
||||
loaded: false,
|
||||
loading: false,
|
||||
download_progress: None,
|
||||
error: Some("Model not available on iOS".to_string()),
|
||||
error: Some("Model not available on mobile platforms".to_string()),
|
||||
model_path: None,
|
||||
}
|
||||
}
|
||||
@@ -97,21 +99,21 @@ pub fn model_get_status() -> ModelStatusResponse {
|
||||
/// This is useful for preloading the model.
|
||||
#[tauri::command]
|
||||
pub async fn model_ensure_loaded() -> Result<(), String> {
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
crate::services::llama::LLAMA_MANAGER.ensure_loaded().await
|
||||
}
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
{
|
||||
Err("Model not available on iOS".to_string())
|
||||
Err("Model not available on mobile platforms".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate text from a prompt.
|
||||
#[tauri::command]
|
||||
pub async fn model_generate(request: GenerateRequest) -> Result<String, String> {
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
use crate::services::llama::GenerateConfig;
|
||||
|
||||
@@ -126,17 +128,17 @@ pub async fn model_generate(request: GenerateRequest) -> Result<String, String>
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
{
|
||||
let _ = request;
|
||||
Err("Model not available on iOS".to_string())
|
||||
Err("Model not available on mobile platforms".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// Summarize text using a built-in prompt.
|
||||
#[tauri::command]
|
||||
pub async fn model_summarize(text: String, max_tokens: Option<u32>) -> Result<String, String> {
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
let tokens = max_tokens.unwrap_or(500);
|
||||
crate::services::llama::LLAMA_MANAGER
|
||||
@@ -144,24 +146,24 @@ pub async fn model_summarize(text: String, max_tokens: Option<u32>) -> Result<St
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
{
|
||||
let _ = (text, max_tokens);
|
||||
Err("Model not available on iOS".to_string())
|
||||
Err("Model not available on mobile platforms".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// Unload the model from memory to free resources.
|
||||
#[tauri::command]
|
||||
pub fn model_unload() -> Result<(), String> {
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
crate::services::llama::LLAMA_MANAGER.unload();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
{
|
||||
Err("Model not available on iOS".to_string())
|
||||
Err("Model not available on mobile platforms".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
+3
-15
@@ -236,7 +236,8 @@ pub fn run() {
|
||||
let mut builder = tauri::Builder::default()
|
||||
// Plugins
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.plugin(tauri_plugin_deep_link::init());
|
||||
.plugin(tauri_plugin_deep_link::init())
|
||||
.plugin(tauri_plugin_os::init());
|
||||
|
||||
// Add desktop-only plugins (autostart, notification)
|
||||
#[cfg(desktop)]
|
||||
@@ -341,20 +342,7 @@ pub fn run() {
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
log::info!("[runtime] V8 runtime disabled on Android");
|
||||
|
||||
// Initialize local model service for Android
|
||||
let data_dir = app
|
||||
.path()
|
||||
.app_data_dir()
|
||||
.unwrap_or_else(|_| {
|
||||
dirs::home_dir()
|
||||
.unwrap_or_else(|| std::path::PathBuf::from("."))
|
||||
.join(".alphahuman")
|
||||
});
|
||||
let model_dir = data_dir.join("models");
|
||||
services::llama::LLAMA_MANAGER.set_data_dir(model_dir);
|
||||
log::info!("[runtime] Local model service initialized for Android");
|
||||
log::info!("[runtime] V8 runtime and local model disabled on Android");
|
||||
}
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
|
||||
@@ -8,20 +8,28 @@
|
||||
//! - Non-MCP server events forwarded to running skills AND to the frontend
|
||||
//! - Connection state emitted to the frontend via Tauri events
|
||||
//! - Automatic reconnection with exponential backoff
|
||||
//!
|
||||
//! Note: On Android, the Rust Socket.io client is not available due to
|
||||
//! native-tls/OpenSSL build complexity. The frontend uses its own Socket.io
|
||||
//! connection instead.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use futures_util::FutureExt;
|
||||
use parking_lot::RwLock;
|
||||
use rust_socketio::{
|
||||
asynchronous::{Client, ClientBuilder},
|
||||
Event, Payload,
|
||||
};
|
||||
use serde_json::json;
|
||||
use tauri::{AppHandle, Emitter};
|
||||
|
||||
use crate::models::socket::{ConnectionStatus, SocketState};
|
||||
|
||||
// rust_socketio only available on non-Android platforms
|
||||
#[cfg(not(target_os = "android"))]
|
||||
use futures_util::FutureExt;
|
||||
#[cfg(not(target_os = "android"))]
|
||||
use rust_socketio::{
|
||||
asynchronous::{Client, ClientBuilder},
|
||||
Event, Payload,
|
||||
};
|
||||
|
||||
// SkillRegistry only available on desktop (V8/deno_core required)
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use crate::runtime::skill_registry::SkillRegistry;
|
||||
|
||||
@@ -6,6 +6,6 @@ pub mod tdlib_v8;
|
||||
#[cfg(desktop)]
|
||||
pub mod notification_service;
|
||||
|
||||
// Local LLM inference - available on desktop and Android (not iOS)
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
// Local LLM inference - desktop only (llama.cpp requires native C++ compilation)
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub mod llama;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { useModelStatus } from '../hooks/useModelStatus';
|
||||
|
||||
interface ModelDownloadProgressProps {
|
||||
@@ -11,6 +13,50 @@ const ModelDownloadProgress = ({
|
||||
}: ModelDownloadProgressProps) => {
|
||||
const { isAvailable, isLoaded, isLoading, downloadProgress, error, ensureLoaded } =
|
||||
useModelStatus();
|
||||
const [isMobile, setIsMobile] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Detect mobile platform
|
||||
const detectMobile = async () => {
|
||||
try {
|
||||
const { platform } = await import('@tauri-apps/plugin-os');
|
||||
const currentPlatform = await platform();
|
||||
setIsMobile(currentPlatform === 'android' || currentPlatform === 'ios');
|
||||
} catch {
|
||||
// If we can't detect platform, assume desktop
|
||||
setIsMobile(false);
|
||||
}
|
||||
};
|
||||
detectMobile();
|
||||
}, []);
|
||||
|
||||
// Show mobile-only message on mobile platforms
|
||||
if (isMobile) {
|
||||
return (
|
||||
<div className={`glass rounded-2xl p-3 shadow-large animate-fade-up ${className}`}>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex-shrink-0">
|
||||
<svg
|
||||
className="w-5 h-5 text-stone-400"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<span className="font-medium text-sm">Local AI Model</span>
|
||||
<p className="text-xs opacity-60">Available on desktop only</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Don't render if not available on this platform
|
||||
if (!isAvailable) {
|
||||
|
||||
@@ -276,6 +276,7 @@ function SkillActionButton({
|
||||
export default function SkillsGrid() {
|
||||
const [skillsList, setSkillsList] = useState<SkillListEntry[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [isMobile, setIsMobile] = useState(false);
|
||||
const [setupModalOpen, setSetupModalOpen] = useState(false);
|
||||
const [managementModalOpen, setManagementModalOpen] = useState(false);
|
||||
const [activeSkillId, setActiveSkillId] = useState<string | null>(null);
|
||||
@@ -288,6 +289,19 @@ export default function SkillsGrid() {
|
||||
const skillStates = useAppSelector(state => state.skills.skillStates);
|
||||
|
||||
useEffect(() => {
|
||||
// Detect mobile platform
|
||||
const detectMobile = async () => {
|
||||
try {
|
||||
const { platform } = await import('@tauri-apps/plugin-os');
|
||||
const currentPlatform = await platform();
|
||||
setIsMobile(currentPlatform === 'android' || currentPlatform === 'ios');
|
||||
} catch {
|
||||
// If we can't detect platform, assume desktop
|
||||
setIsMobile(false);
|
||||
}
|
||||
};
|
||||
detectMobile();
|
||||
|
||||
// Load skills from the V8 runtime engine.
|
||||
const loadSkills = async () => {
|
||||
try {
|
||||
@@ -355,7 +369,38 @@ export default function SkillsGrid() {
|
||||
});
|
||||
}, [skillsList, skillsState, skillStates]);
|
||||
|
||||
// If loading or no skills, don't render
|
||||
// Show mobile-only message on mobile platforms
|
||||
if (!loading && isMobile) {
|
||||
return (
|
||||
<div className="animate-fade-up mt-4 mb-8 relative">
|
||||
<h3 className="text-sm font-semibold text-white mb-3 px-1 opacity-80 text-center">
|
||||
Skills
|
||||
</h3>
|
||||
<div className="glass rounded-xl p-4 text-center">
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<svg
|
||||
className="w-8 h-8 text-stone-400"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={1.5}
|
||||
d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
<p className="text-sm text-stone-400">Skills are available on desktop only</p>
|
||||
<p className="text-xs text-stone-500">
|
||||
Use the desktop app to configure and run skills
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// If loading or no skills on desktop, don't render
|
||||
if (loading || skillsList.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -859,6 +859,13 @@
|
||||
dependencies:
|
||||
"@tauri-apps/api" "^2.8.0"
|
||||
|
||||
"@tauri-apps/plugin-os@^2.3.2":
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-os/-/plugin-os-2.3.2.tgz#de916a82d8d955bba59a2ffdba7f7aaa29397246"
|
||||
integrity sha512-n+nXWeuSeF9wcEsSPmRnBEGrRgOy6jjkSU+UVCOV8YUGKb2erhDOxis7IqRXiRVHhY8XMKks00BJ0OAdkpf6+A==
|
||||
dependencies:
|
||||
"@tauri-apps/api" "^2.8.0"
|
||||
|
||||
"@tauri-apps/plugin-shell@~2":
|
||||
version "2.3.4"
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-shell/-/plugin-shell-2.3.4.tgz#26cbfca659d127da81b0d4583dec9a56d13f5dfe"
|
||||
|
||||
Reference in New Issue
Block a user