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:
parent
5486318321
commit
09356bb28e
11 changed files with 88 additions and 4671 deletions
4350
Cargo.lock
generated
4350
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
23
Cargo.toml
23
Cargo.toml
|
|
@ -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" }
|
||||
|
|
|
|||
12
build.rs
12
build.rs
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
30
src/lib.rs
30
src/lib.rs
|
|
@ -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::*;
|
||||
|
|
|
|||
30
src/main.rs
30
src/main.rs
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
||||
|
|
|
|||
|
|
@ -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": []
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue