From 21e51dabf34e0f51faa6444287e6e83adbcfeb3e Mon Sep 17 00:00:00 2001 From: xintaofei Date: Wed, 25 Mar 2026 23:07:59 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81web=E7=AB=AF=E8=AE=BF?= =?UTF-8?q?=E9=97=AE=E6=9B=B4=E6=96=B0/web=E7=AE=A1=E7=90=86=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-tauri/src/web/handlers/mod.rs | 1 + src-tauri/src/web/handlers/web_server.rs | 52 +++++++++++++++++ src-tauri/src/web/mod.rs | 74 ++++++++++++++---------- src-tauri/src/web/router.rs | 5 ++ src/lib/updater.ts | 11 +++- 5 files changed, 112 insertions(+), 31 deletions(-) create mode 100644 src-tauri/src/web/handlers/web_server.rs diff --git a/src-tauri/src/web/handlers/mod.rs b/src-tauri/src/web/handlers/mod.rs index 60a754d..3fde039 100644 --- a/src-tauri/src/web/handlers/mod.rs +++ b/src-tauri/src/web/handlers/mod.rs @@ -9,3 +9,4 @@ pub mod version_control; pub mod folder_commands; pub mod mcp; pub mod git; +pub mod web_server; diff --git a/src-tauri/src/web/handlers/web_server.rs b/src-tauri/src/web/handlers/web_server.rs new file mode 100644 index 0000000..3d35c6c --- /dev/null +++ b/src-tauri/src/web/handlers/web_server.rs @@ -0,0 +1,52 @@ +use axum::{extract::Extension, Json}; +use serde::{Deserialize, Serialize}; +use tauri::Manager; + +use crate::app_error::AppCommandError; +use crate::web::{do_get_web_server_status, do_start_web_server, do_stop_web_server}; +use crate::web::{WebServerInfo, WebServerState}; + +pub async fn get_web_server_status( + Extension(app): Extension, +) -> Result>, AppCommandError> { + let state = app.state::(); + Ok(Json(do_get_web_server_status(&state))) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct StartWebServerParams { + pub port: Option, + pub host: Option, +} + +pub async fn start_web_server( + Extension(app): Extension, + Json(params): Json, +) -> Result, AppCommandError> { + let state = app.state::(); + let info = do_start_web_server(&app, &state, params.port, params.host).await?; + Ok(Json(info)) +} + +pub async fn stop_web_server( + Extension(app): Extension, +) -> Result, AppCommandError> { + let state = app.state::(); + do_stop_web_server(&state); + Ok(Json(())) +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AppUpdateCheckResult { + pub current_version: &'static str, + pub update: Option<()>, +} + +pub async fn check_app_update() -> Json { + Json(AppUpdateCheckResult { + current_version: env!("CARGO_PKG_VERSION"), + update: None, + }) +} diff --git a/src-tauri/src/web/mod.rs b/src-tauri/src/web/mod.rs index 357a6f9..e2c44ae 100644 --- a/src-tauri/src/web/mod.rs +++ b/src-tauri/src/web/mod.rs @@ -40,11 +40,11 @@ pub struct WebServerInfo { pub addresses: Vec, } -fn generate_random_token() -> String { +pub(crate) fn generate_random_token() -> String { uuid::Uuid::new_v4().to_string().replace('-', "") } -fn find_static_dir(app: &tauri::AppHandle) -> PathBuf { +pub(crate) fn find_static_dir(app: &tauri::AppHandle) -> PathBuf { // 1. Production: Tauri bundles frontendDist into the resource directory. let resource = app.path().resource_dir().ok(); if let Some(ref dir) = resource { @@ -83,7 +83,7 @@ fn find_static_dir(app: &tauri::AppHandle) -> PathBuf { cwd_out } -fn get_local_addresses(port: u16) -> Vec { +pub(crate) fn get_local_addresses(port: u16) -> Vec { let mut addrs = vec![format!("http://127.0.0.1:{}", port)]; // Try to get LAN IPs if let Ok(interfaces) = std::net::UdpSocket::bind("0.0.0.0:0") { @@ -97,14 +97,14 @@ fn get_local_addresses(port: u16) -> Vec { addrs } -#[tauri::command] -pub async fn start_web_server( - app: tauri::AppHandle, - state: tauri::State<'_, WebServerState>, +// ── Core logic (shared by Tauri commands and web handlers) ── + +pub(crate) async fn do_start_web_server( + app: &tauri::AppHandle, + state: &WebServerState, port: Option, host: Option, ) -> Result { - // Check if already running if state.running.load(Ordering::Relaxed) { return Err(AppCommandError::new( AppErrorCode::AlreadyExists, @@ -116,11 +116,7 @@ pub async fn start_web_server( let host = host.unwrap_or_else(|| "0.0.0.0".to_string()); let token = generate_random_token(); - // Determine static directory for serving the frontend. - // In production: files are bundled into the resource directory. - // In dev: the "out/" directory is at the project root (one level above src-tauri/). - let static_dir = find_static_dir(&app); - + let static_dir = find_static_dir(app); let router = router::build_router(app.clone(), token.clone(), static_dir); let addr: SocketAddr = format!("{}:{}", host, port) @@ -142,14 +138,12 @@ pub async fn start_web_server( } }); - // Store state *state.handle.lock().unwrap() = Some(handle); state.port.store(actual_port, Ordering::Relaxed); *state.token.lock().unwrap() = token.clone(); state.running.store(true, Ordering::Relaxed); let addresses = get_local_addresses(actual_port); - Ok(WebServerInfo { port: actual_port, token, @@ -157,10 +151,7 @@ pub async fn start_web_server( }) } -#[tauri::command] -pub async fn stop_web_server( - state: tauri::State<'_, WebServerState>, -) -> Result<(), AppCommandError> { +pub(crate) fn do_stop_web_server(state: &WebServerState) { if let Some(handle) = state.handle.lock().unwrap().take() { handle.abort(); } @@ -168,6 +159,39 @@ pub async fn stop_web_server( state.port.store(0, Ordering::Relaxed); *state.token.lock().unwrap() = String::new(); eprintln!("[WEB] Web server stopped"); +} + +pub(crate) fn do_get_web_server_status(state: &WebServerState) -> Option { + if !state.running.load(Ordering::Relaxed) { + return None; + } + let port = state.port.load(Ordering::Relaxed); + let token = state.token.lock().unwrap().clone(); + let addresses = get_local_addresses(port); + Some(WebServerInfo { + port, + token, + addresses, + }) +} + +// ── Tauri commands (thin wrappers) ── + +#[tauri::command] +pub async fn start_web_server( + app: tauri::AppHandle, + state: tauri::State<'_, WebServerState>, + port: Option, + host: Option, +) -> Result { + do_start_web_server(&app, &state, port, host).await +} + +#[tauri::command] +pub async fn stop_web_server( + state: tauri::State<'_, WebServerState>, +) -> Result<(), AppCommandError> { + do_stop_web_server(&state); Ok(()) } @@ -175,15 +199,5 @@ pub async fn stop_web_server( pub async fn get_web_server_status( state: tauri::State<'_, WebServerState>, ) -> Result, AppCommandError> { - if !state.running.load(Ordering::Relaxed) { - return Ok(None); - } - let port = state.port.load(Ordering::Relaxed); - let token = state.token.lock().unwrap().clone(); - let addresses = get_local_addresses(port); - Ok(Some(WebServerInfo { - port, - token, - addresses, - })) + Ok(do_get_web_server_status(&state)) } diff --git a/src-tauri/src/web/router.rs b/src-tauri/src/web/router.rs index 000177b..162cbd1 100644 --- a/src-tauri/src/web/router.rs +++ b/src-tauri/src/web/router.rs @@ -167,6 +167,11 @@ pub fn build_router(app: tauri::AppHandle, token: String, static_dir: std::path: .route("/acp_read_agent_skill", post(handlers::acp::acp_read_agent_skill)) .route("/acp_save_agent_skill", post(handlers::acp::acp_save_agent_skill)) .route("/acp_delete_agent_skill", post(handlers::acp::acp_delete_agent_skill)) + // ─── Web Server ─── + .route("/get_web_server_status", post(handlers::web_server::get_web_server_status)) + .route("/start_web_server", post(handlers::web_server::start_web_server)) + .route("/stop_web_server", post(handlers::web_server::stop_web_server)) + .route("/check_app_update", post(handlers::web_server::check_app_update)) // ─── Terminal ─── .route("/terminal_spawn", post(handlers::terminal::terminal_spawn)) .route("/terminal_write", post(handlers::terminal::terminal_write)) diff --git a/src/lib/updater.ts b/src/lib/updater.ts index 2016368..f53d3f6 100644 --- a/src/lib/updater.ts +++ b/src/lib/updater.ts @@ -1,3 +1,5 @@ +import { getTransport, isDesktop } from "./transport" + // All updater imports are dynamic to avoid crashing in non-Tauri browsers. // eslint-disable-next-line @typescript-eslint/no-explicit-any type Update = any @@ -20,15 +22,22 @@ export interface AppUpdateErrorInfo { } export async function getCurrentAppVersion(): Promise { + if (!isDesktop()) { + const result = await getTransport().call("check_app_update") + return result.currentVersion + } try { const { getVersion } = await import("@tauri-apps/api/app") return await getVersion() } catch { - return "web" + return "unknown" } } export async function checkAppUpdate(): Promise { + if (!isDesktop()) { + return getTransport().call("check_app_update") + } const { getVersion } = await import("@tauri-apps/api/app") const { check } = await import("@tauri-apps/plugin-updater") const [currentVersion, update] = await Promise.all([getVersion(), check()])