Remove Tauri dependencies - botui is now pure web

Tauri-specific code has been moved to the botapp crate.

Changes:
- Removed tauri, tauri-plugin-*, trayicon, ksni dependencies
- Removed desktop feature flags
- Removed src/desktop/ module (moved to botapp)
- Removed tauri.conf.json (moved to botapp)
- Simplified build.rs (no tauri_build)
- Updated lib.rs and main.rs for pure web operation

Architecture:
- botui: Pure web UI (this crate) - no native deps
- botapp: Tauri wrapper that loads botui's suite

This separation allows:
- Same UI code for web, desktop, and mobile
- Clean dependency management
- App-specific features only in native app
This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2025-12-04 09:03:03 -03:00
parent 5486318321
commit 09356bb28e
11 changed files with 88 additions and 4671 deletions

4350
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,7 @@
name = "botui"
version = "6.1.0"
edition = "2021"
description = "General Bots UI - Desktop, Web and Mobile interface"
description = "General Bots UI - Pure web interface"
license = "AGPL-3.0"
[dependencies.botlib]
@ -10,11 +10,8 @@ path = "../botlib"
features = ["http-client"]
[features]
default = ["desktop", "ui-server"]
desktop = ["dep:tauri", "dep:tauri-plugin-dialog", "dep:tauri-plugin-opener"]
desktop-tray = ["desktop", "dep:trayicon", "dep:ksni"]
default = ["ui-server"]
ui-server = []
mobile = []
[dependencies]
anyhow = "1.0"
@ -32,7 +29,6 @@ futures = "0.3"
futures-util = "0.3"
hostname = "0.4"
jsonwebtoken = "9.3"
ksni = { version = "0.2", optional = true }
local-ip-address = "0.6.5"
log = "0.4"
mime_guess = "2.0"
@ -41,9 +37,6 @@ regex = "1.10"
reqwest = { version = "0.12", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tauri = { version = "2", features = ["unstable"], optional = true }
tauri-plugin-dialog = { version = "2", optional = true }
tauri-plugin-opener = { version = "2", optional = true }
time = "0.3"
tokio = { version = "1.41", features = ["full"] }
tokio-stream = "0.1"
@ -51,18 +44,6 @@ tower = "0.4"
tower-http = { version = "0.5", features = ["cors", "fs", "trace"] }
tower-cookies = "0.10"
tracing = "0.1"
trayicon = { version = "0.2", optional = true }
urlencoding = "2.1"
uuid = { version = "1.11", features = ["serde", "v4"] }
webbrowser = "0.8"
[target.'cfg(unix)'.dependencies]
ksni = { version = "0.2", optional = true }
[target.'cfg(windows)'.dependencies]
trayicon = { version = "0.2", optional = true }
image = "0.25"
thiserror = "2.0"
[build-dependencies]
tauri-build = { version = "2" }

View file

@ -1,7 +1,9 @@
//! Build script for botui
//!
//! This is a minimal build script since botui is now a pure web project.
//! Tauri build requirements have been moved to the botapp crate.
fn main() {
// Only run tauri_build when the desktop feature is enabled
#[cfg(feature = "desktop")]
{
tauri_build::build()
}
// No build steps required for pure web UI
// Tauri-specific builds are now in botapp
}

View file

@ -1,93 +0,0 @@
#![cfg(feature = "desktop")]
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::{Path, PathBuf};
use tauri::{Emitter, Window};
#[derive(Debug, Serialize, Deserialize)]
pub struct FileItem {
name: String,
path: String,
is_dir: bool,
}
#[tauri::command]
#[allow(dead_code)]
pub fn list_files(path: &str) -> Result<Vec<FileItem>, String> {
let base_path = Path::new(path);
let mut files = Vec::new();
if !base_path.exists() {
return Err("Path does not exist".into());
}
for entry in fs::read_dir(base_path).map_err(|e| e.to_string())? {
let entry = entry.map_err(|e| e.to_string())?;
let path = entry.path();
let name = path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("")
.to_string();
files.push(FileItem {
name,
path: path.to_str().unwrap_or("").to_string(),
is_dir: path.is_dir(),
});
}
files.sort_by(|a, b| {
if a.is_dir && !b.is_dir {
std::cmp::Ordering::Less
} else if !a.is_dir && b.is_dir {
std::cmp::Ordering::Greater
} else {
a.name.cmp(&b.name)
}
});
Ok(files)
}
#[tauri::command]
#[allow(dead_code)]
pub async fn upload_file(
window: Window,
src_path: String,
dest_path: String,
) -> Result<(), String> {
use std::fs::File;
use std::io::{Read, Write};
let src = PathBuf::from(&src_path);
let dest_dir = PathBuf::from(&dest_path);
let dest = dest_dir.join(src.file_name().ok_or("Invalid source file")?);
if !dest_dir.exists() {
fs::create_dir_all(&dest_dir).map_err(|e| e.to_string())?;
}
let mut source_file = File::open(&src).map_err(|e| e.to_string())?;
let mut dest_file = File::create(&dest).map_err(|e| e.to_string())?;
let file_size = source_file.metadata().map_err(|e| e.to_string())?.len();
let mut buffer = [0; 8192];
let mut total_read = 0;
loop {
let bytes_read = source_file.read(&mut buffer).map_err(|e| e.to_string())?;
if bytes_read == 0 {
break;
}
dest_file
.write_all(&buffer[..bytes_read])
.map_err(|e| e.to_string())?;
total_read += bytes_read as u64;
let progress = (total_read as f64 / file_size as f64) * 100.0;
window
.emit("upload_progress", progress)
.map_err(|e| e.to_string())?;
}
Ok(())
}
#[tauri::command]
#[allow(dead_code)]
pub fn create_folder(path: String, name: String) -> Result<(), String> {
let full_path = Path::new(&path).join(&name);
if full_path.exists() {
return Err("Folder already exists".into());
}
fs::create_dir(full_path).map_err(|e| e.to_string())?;
Ok(())
}

View file

@ -1,12 +0,0 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
//! Desktop Module
//!
//! This module provides desktop-specific functionality including:
//! - Drive management
//! - System tray management
#[cfg(feature = "desktop")]
pub mod drive;
#[cfg(feature = "desktop")]
pub mod tray;

View file

@ -1,182 +0,0 @@
#![cfg(feature = "desktop")]
#![allow(dead_code)]
use anyhow::Result;
use std::sync::Arc;
use tokio::sync::RwLock;
#[cfg(target_os = "windows")]
use trayicon::{Icon, MenuBuilder, TrayIcon, TrayIconBuilder};
#[cfg(target_os = "macos")]
use trayicon_osx::{Icon, MenuBuilder, TrayIcon, TrayIconBuilder};
#[cfg(all(target_os = "linux", feature = "desktop-tray"))]
use ksni::{Tray, TrayService};
pub struct TrayManager {
hostname: Arc<RwLock<Option<String>>>,
running_mode: RunningMode,
}
#[derive(Debug, Clone, PartialEq)]
pub enum RunningMode {
Server,
Desktop,
Client,
}
impl TrayManager {
pub fn new() -> Self {
let running_mode = if cfg!(feature = "desktop") {
RunningMode::Desktop
} else {
RunningMode::Server
};
Self {
hostname: Arc::new(RwLock::new(None)),
running_mode,
}
}
pub async fn start(&self) -> Result<()> {
match self.running_mode {
RunningMode::Desktop => {
self.start_desktop_mode().await?;
}
RunningMode::Server => {
log::info!("Running in server mode - tray icon disabled");
}
RunningMode::Client => {
log::info!("Running in client mode - tray icon minimal");
}
}
Ok(())
}
async fn start_desktop_mode(&self) -> Result<()> {
log::info!("Starting desktop mode tray icon");
#[cfg(any(target_os = "windows", target_os = "macos"))]
{
self.create_tray_icon()?;
}
#[cfg(target_os = "linux")]
{
self.create_linux_tray()?;
}
Ok(())
}
#[cfg(any(target_os = "windows", target_os = "macos"))]
fn create_tray_icon(&self) -> Result<()> {
log::info!("Tray icon not fully implemented for this platform");
Ok(())
}
#[cfg(target_os = "linux")]
fn create_linux_tray(&self) -> Result<()> {
log::info!("Linux tray icon not fully implemented");
Ok(())
}
fn get_mode_string(&self) -> String {
match self.running_mode {
RunningMode::Desktop => "Desktop".to_string(),
RunningMode::Server => "Server".to_string(),
RunningMode::Client => "Client".to_string(),
}
}
pub async fn update_status(&self, status: &str) -> Result<()> {
log::info!("Tray status update: {}", status);
Ok(())
}
pub async fn get_hostname(&self) -> Option<String> {
let hostname = self.hostname.read().await;
hostname.clone()
}
}
// Service status monitor
pub struct ServiceMonitor {
services: Vec<ServiceStatus>,
}
#[derive(Debug, Clone)]
pub struct ServiceStatus {
pub name: String,
pub running: bool,
pub port: u16,
pub url: String,
}
impl ServiceMonitor {
pub fn new() -> Self {
Self {
services: vec![
ServiceStatus {
name: "API".to_string(),
running: false,
port: 8080,
url: "https://localhost:8080".to_string(),
},
ServiceStatus {
name: "Directory".to_string(),
running: false,
port: 8080,
url: "https://localhost:8080".to_string(),
},
ServiceStatus {
name: "LLM".to_string(),
running: false,
port: 8081,
url: "https://localhost:8081".to_string(),
},
ServiceStatus {
name: "Database".to_string(),
running: false,
port: 5432,
url: "postgresql://localhost:5432".to_string(),
},
ServiceStatus {
name: "Cache".to_string(),
running: false,
port: 6379,
url: "redis://localhost:6379".to_string(),
},
],
}
}
pub async fn check_services(&mut self) -> Vec<ServiceStatus> {
let urls: Vec<String> = self.services.iter().map(|s| s.url.clone()).collect();
for (i, url) in urls.iter().enumerate() {
self.services[i].running = self.check_service(url).await;
}
self.services.clone()
}
async fn check_service(&self, url: &str) -> bool {
if url.starts_with("https://") || url.starts_with("http://") {
match reqwest::Client::builder()
.danger_accept_invalid_certs(true)
.build()
.unwrap()
.get(format!("{}/health", url))
.timeout(std::time::Duration::from_secs(2))
.send()
.await
{
Ok(_) => true,
Err(_) => false,
}
} else {
false
}
}
}

View file

@ -1,13 +1,12 @@
//! BotUI - General Bots Desktop, Web & Mobile UI
//! BotUI - General Bots Pure Web UI
//!
//! This crate provides the UI layer for General Bots including:
//! - Desktop application (Tauri)
//! - Web UI server (HTMX backend)
//! This crate provides the web UI layer for General Bots:
//! - Serves static HTMX UI files (suite, minimal)
//! - Proxies API requests to botserver
//! - WebSocket support for real-time communication
//!
//! Most logic lives in botserver; this crate is primarily for:
//! - Serving static HTMX UI files
//! - Proxying API requests to botserver
//! - Desktop-specific functionality (Tauri)
//! For desktop/mobile native features, see the `botapp` crate which
//! wraps this pure web UI with Tauri.
// Re-export common types from botlib
pub use botlib::{
@ -18,25 +17,16 @@ pub use botlib::{
// HTTP client is always available via botlib
pub use botlib::BotServerClient;
#[cfg(feature = "desktop")]
pub mod desktop;
#[cfg(not(feature = "desktop"))]
pub mod http_client;
pub mod shared;
#[cfg(not(feature = "desktop"))]
#[cfg(feature = "ui-server")]
pub mod ui_server;
#[cfg(not(feature = "desktop"))]
#[cfg(feature = "ui-server")]
pub mod web;
// Re-exports
#[cfg(feature = "desktop")]
pub use desktop::*;
pub use shared::*;
#[cfg(not(feature = "desktop"))]
#[cfg(feature = "ui-server")]
pub use ui_server::*;

View file

@ -1,38 +1,20 @@
#![cfg_attr(feature = "desktop", windows_subsystem = "windows")]
//! BotUI - General Bots Pure Web UI Server
//!
//! This is the entry point for the botui web server.
//! For desktop/mobile native features, see the `botapp` crate.
use log::info;
#[cfg(feature = "desktop")]
mod desktop;
mod shared;
mod ui_server;
#[cfg(not(feature = "desktop"))]
pub mod http_client;
#[cfg(not(feature = "desktop"))]
mod web;
#[tokio::main]
async fn main() -> std::io::Result<()> {
env_logger::init();
info!("BotUI starting...");
info!("Starting web UI server...");
#[cfg(feature = "desktop")]
{
info!("Starting in desktop mode (Tauri)...");
return Ok(());
}
#[cfg(not(feature = "desktop"))]
{
info!("Starting web UI server...");
web_main().await
}
}
#[cfg(not(feature = "desktop"))]
async fn web_main() -> std::io::Result<()> {
let app = ui_server::configure_router();
let addr = std::net::SocketAddr::from(([0, 0, 0, 0], 3000));

View file

@ -1,4 +1,6 @@
#![cfg(not(feature = "desktop"))]
//! UI Server module for BotUI
//!
//! Serves the web UI (suite, minimal) and handles API proxying.
use axum::{
extract::State,

View file

@ -1,6 +1,6 @@
//! Web module with basic data structures
#![cfg(not(feature = "desktop"))]
//!
//! Contains DTOs and types for the web API layer.
use serde::{Deserialize, Serialize};

View file

@ -1,19 +0,0 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "General Bots",
"version": "6.0.8",
"identifier": "br.com.pragmatismo",
"build": {
"frontendDist": "./ui/suite"
},
"app": {
"security": {
"csp": null
}
},
"bundle": {
"active": true,
"targets": "all",
"icon": []
}
}