支持无GUI的Server运行模式

This commit is contained in:
xintaofei
2026-03-29 18:36:30 +08:00
parent 7b73d7e1c2
commit 080a16f26c
49 changed files with 2169 additions and 1047 deletions

View File

@@ -1,3 +1,5 @@
use std::sync::Arc;
use serde::Serialize;
use tokio::sync::broadcast;
@@ -34,15 +36,33 @@ impl WebEventBroadcaster {
}
}
/// Unified event emission: sends to both Tauri webview and Web clients.
/// Abstraction over event emission targets.
/// In Tauri mode, events go to both webview and WebSocket clients.
/// In standalone server mode, events only go to WebSocket clients.
#[derive(Clone)]
pub enum EventEmitter {
#[cfg(feature = "tauri-runtime")]
Tauri(tauri::AppHandle),
WebOnly(Arc<WebEventBroadcaster>),
}
/// Unified event emission: sends to both Tauri webview and Web clients (if applicable).
pub fn emit_event(
app: &tauri::AppHandle,
emitter: &EventEmitter,
event: &str,
payload: impl Serialize + Clone,
) {
use tauri::{Emitter, Manager};
let _ = app.emit(event, payload.clone());
if let Some(web) = app.try_state::<WebEventBroadcaster>() {
web.send(event, &payload);
match emitter {
#[cfg(feature = "tauri-runtime")]
EventEmitter::Tauri(app) => {
use tauri::{Emitter, Manager};
let _ = app.emit(event, payload.clone());
if let Some(web) = app.try_state::<Arc<WebEventBroadcaster>>() {
web.send(event, &payload);
}
}
EventEmitter::WebOnly(broadcaster) => {
broadcaster.send(event, &payload);
}
}
}

View File

@@ -1,10 +1,9 @@
use std::collections::BTreeMap;
use std::sync::Arc;
use axum::{extract::Extension, Json};
use serde::Deserialize;
use tauri::Manager;
use crate::acp::manager::ConnectionManager;
use crate::acp::preflight::PreflightResult;
use crate::acp::registry;
use crate::acp::types::{
@@ -12,9 +11,9 @@ use crate::acp::types::{
AgentSkillsListResult, ConnectionInfo, ForkResultInfo,
};
use crate::app_error::AppCommandError;
use crate::app_state::AppState;
use crate::commands::acp as acp_commands;
use crate::db::service::agent_setting_service;
use crate::db::AppDatabase;
use crate::models::agent::AgentType;
#[derive(Deserialize)]
@@ -24,21 +23,21 @@ pub struct AgentTypeParams {
}
pub async fn acp_get_agent_status(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<AgentTypeParams>,
) -> Result<Json<AcpAgentStatus>, AppCommandError> {
let db = app.state::<crate::db::AppDatabase>();
let result = acp_commands::acp_get_agent_status(params.agent_type, db)
let db = &state.db;
let result = acp_commands::acp_get_agent_status_core(params.agent_type, db)
.await
.map_err(|e| AppCommandError::task_execution_failed(e.to_string()))?;
Ok(Json(result))
}
pub async fn acp_list_agents(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
) -> Result<Json<Vec<AcpAgentInfo>>, AppCommandError> {
let db = app.state::<crate::db::AppDatabase>();
let result = acp_commands::acp_list_agents(db)
let db = &state.db;
let result = acp_commands::acp_list_agents_core(db)
.await
.map_err(|e| AppCommandError::task_execution_failed(e.to_string()))?;
Ok(Json(result))
@@ -53,11 +52,11 @@ pub struct AcpConnectParams {
}
pub async fn acp_connect(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<AcpConnectParams>,
) -> Result<Json<String>, AppCommandError> {
let db = app.state::<AppDatabase>();
let manager = app.state::<ConnectionManager>();
let db = &state.db;
let manager = &state.connection_manager;
let meta = registry::get_agent_meta(params.agent_type);
let setting = agent_setting_service::get_by_agent_type(&db.conn, params.agent_type)
@@ -94,6 +93,7 @@ pub async fn acp_connect(
}
}
let emitter = state.emitter.clone();
let connection_id = manager
.spawn_agent(
params.agent_type,
@@ -101,7 +101,7 @@ pub async fn acp_connect(
params.session_id,
runtime_env,
"web".to_string(),
app.clone(),
emitter,
)
.await
.map_err(|e| AppCommandError::task_execution_failed(e.to_string()))?;
@@ -116,10 +116,10 @@ pub struct AcpDisconnectParams {
}
pub async fn acp_disconnect(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<AcpDisconnectParams>,
) -> Result<Json<()>, AppCommandError> {
let manager = app.state::<ConnectionManager>();
let manager = &state.connection_manager;
manager
.disconnect(&params.connection_id)
.await
@@ -135,10 +135,10 @@ pub struct AcpPromptParams {
}
pub async fn acp_prompt(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<AcpPromptParams>,
) -> Result<Json<()>, AppCommandError> {
let manager = app.state::<ConnectionManager>();
let manager = &state.connection_manager;
manager
.send_prompt(&params.connection_id, params.blocks)
.await
@@ -279,10 +279,10 @@ pub struct AcpSetModeParams {
}
pub async fn acp_set_mode(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<AcpSetModeParams>,
) -> Result<Json<()>, AppCommandError> {
let manager = app.state::<ConnectionManager>();
let manager = &state.connection_manager;
manager
.set_mode(&params.connection_id, params.mode_id)
.await
@@ -299,10 +299,10 @@ pub struct AcpSetConfigOptionParams {
}
pub async fn acp_set_config_option(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<AcpSetConfigOptionParams>,
) -> Result<Json<()>, AppCommandError> {
let manager = app.state::<ConnectionManager>();
let manager = &state.connection_manager;
manager
.set_config_option(&params.connection_id, params.config_id, params.value_id)
.await
@@ -311,10 +311,10 @@ pub async fn acp_set_config_option(
}
pub async fn acp_cancel(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<AcpConnectionIdParams>,
) -> Result<Json<()>, AppCommandError> {
let manager = app.state::<ConnectionManager>();
let manager = &state.connection_manager;
manager
.cancel(&params.connection_id)
.await
@@ -323,10 +323,10 @@ pub async fn acp_cancel(
}
pub async fn acp_fork(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<AcpConnectionIdParams>,
) -> Result<Json<ForkResultInfo>, AppCommandError> {
let manager = app.state::<ConnectionManager>();
let manager = &state.connection_manager;
let result = manager
.fork_session(&params.connection_id)
.await
@@ -343,10 +343,10 @@ pub struct AcpRespondPermissionParams {
}
pub async fn acp_respond_permission(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<AcpRespondPermissionParams>,
) -> Result<Json<()>, AppCommandError> {
let manager = app.state::<ConnectionManager>();
let manager = &state.connection_manager;
manager
.respond_permission(&params.connection_id, &params.request_id, &params.option_id)
.await
@@ -355,9 +355,9 @@ pub async fn acp_respond_permission(
}
pub async fn acp_list_connections(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
) -> Result<Json<Vec<ConnectionInfo>>, AppCommandError> {
let manager = app.state::<ConnectionManager>();
let manager = &state.connection_manager;
let result = manager.list_connections().await;
Ok(Json(result))
}
@@ -377,10 +377,11 @@ pub struct AcpUpdateAgentPreferencesParams {
}
pub async fn acp_update_agent_preferences(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<AcpUpdateAgentPreferencesParams>,
) -> Result<Json<()>, AppCommandError> {
let db = app.state::<AppDatabase>();
let db = &state.db;
let emitter = state.emitter.clone();
acp_commands::acp_update_agent_preferences_core(
params.agent_type,
params.enabled,
@@ -390,7 +391,7 @@ pub async fn acp_update_agent_preferences(
params.codex_auth_json,
params.codex_config_toml,
&db,
&app,
&emitter,
)
.await
.map_err(|e| AppCommandError::task_execution_failed(e.to_string()))?;
@@ -398,20 +399,21 @@ pub async fn acp_update_agent_preferences(
}
pub async fn acp_download_agent_binary(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<AgentTypeParams>,
) -> Result<Json<()>, AppCommandError> {
acp_commands::acp_download_agent_binary_core(params.agent_type, &app)
let emitter = state.emitter.clone();
acp_commands::acp_download_agent_binary_core(params.agent_type, &emitter)
.await
.map_err(|e| AppCommandError::task_execution_failed(e.to_string()))?;
Ok(Json(()))
}
pub async fn acp_detect_agent_local_version(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<AgentTypeParams>,
) -> Result<Json<Option<String>>, AppCommandError> {
let db = app.state::<AppDatabase>();
let db = &state.db;
let result =
acp_commands::acp_detect_agent_local_version_core(params.agent_type, &db.conn)
.await
@@ -427,15 +429,16 @@ pub struct AcpPrepareNpxAgentParams {
}
pub async fn acp_prepare_npx_agent(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<AcpPrepareNpxAgentParams>,
) -> Result<Json<String>, AppCommandError> {
let db = app.state::<AppDatabase>();
let db = &state.db;
let emitter = state.emitter.clone();
let result = acp_commands::acp_prepare_npx_agent_core(
params.agent_type,
params.registry_version,
&db,
&app,
&emitter,
)
.await
.map_err(|e| AppCommandError::task_execution_failed(e.to_string()))?;
@@ -443,11 +446,12 @@ pub async fn acp_prepare_npx_agent(
}
pub async fn acp_uninstall_agent(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<AgentTypeParams>,
) -> Result<Json<()>, AppCommandError> {
let db = app.state::<AppDatabase>();
acp_commands::acp_uninstall_agent_core(params.agent_type, &db, &app)
let db = &state.db;
let emitter = state.emitter.clone();
acp_commands::acp_uninstall_agent_core(params.agent_type, &db, &emitter)
.await
.map_err(|e| AppCommandError::task_execution_failed(e.to_string()))?;
Ok(Json(()))
@@ -460,11 +464,12 @@ pub struct AcpReorderAgentsParams {
}
pub async fn acp_reorder_agents(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<AcpReorderAgentsParams>,
) -> Result<Json<()>, AppCommandError> {
let db = app.state::<AppDatabase>();
acp_commands::acp_reorder_agents_core(&params.agent_types, &db, &app)
let db = &state.db;
let emitter = state.emitter.clone();
acp_commands::acp_reorder_agents_core(&params.agent_types, &db, &emitter)
.await
.map_err(|e| AppCommandError::task_execution_failed(e.to_string()))?;
Ok(Json(()))

View File

@@ -1,11 +1,12 @@
use std::sync::Arc;
use axum::{extract::Extension, Json};
use serde::Deserialize;
use tauri::Manager;
use crate::app_error::AppCommandError;
use crate::app_state::AppState;
use crate::commands::conversations as conv_commands;
use crate::db::service::{conversation_service, folder_service, import_service};
use crate::db::AppDatabase;
use crate::models::*;
#[derive(Deserialize)]
@@ -19,10 +20,10 @@ pub struct ListFolderConversationsParams {
}
pub async fn list_folder_conversations(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<ListFolderConversationsParams>,
) -> Result<Json<Vec<DbConversationSummary>>, AppCommandError> {
let db = app.state::<AppDatabase>();
let db = &state.db;
let result = conversation_service::list_by_folder(
&db.conn,
params.folder_id,
@@ -80,10 +81,10 @@ pub struct GetFolderConversationParams {
}
pub async fn get_folder_conversation(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<GetFolderConversationParams>,
) -> Result<Json<DbConversationDetail>, AppCommandError> {
let db = app.state::<AppDatabase>();
let db = &state.db;
let result =
conv_commands::get_folder_conversation_core(&db.conn, params.conversation_id).await?;
Ok(Json(result))
@@ -111,10 +112,10 @@ pub struct ImportLocalConversationsParams {
}
pub async fn import_local_conversations(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<ImportLocalConversationsParams>,
) -> Result<Json<ImportResult>, AppCommandError> {
let db = app.state::<AppDatabase>();
let db = &state.db;
let folder = folder_service::get_folder_by_id(&db.conn, params.folder_id)
.await
.map_err(AppCommandError::from)?
@@ -134,10 +135,10 @@ pub struct CreateConversationParams {
}
pub async fn create_conversation(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<CreateConversationParams>,
) -> Result<Json<i32>, AppCommandError> {
let db = app.state::<AppDatabase>();
let db = &state.db;
let result = conv_commands::create_conversation_core(
&db.conn,
params.folder_id,
@@ -156,10 +157,10 @@ pub struct UpdateConversationStatusParams {
}
pub async fn update_conversation_status(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<UpdateConversationStatusParams>,
) -> Result<Json<()>, AppCommandError> {
let db = app.state::<AppDatabase>();
let db = &state.db;
let status_enum: crate::db::entities::conversation::ConversationStatus =
serde_json::from_value(serde_json::Value::String(params.status)).map_err(|e| {
AppCommandError::invalid_input("Invalid conversation status").with_detail(e.to_string())
@@ -178,10 +179,10 @@ pub struct UpdateConversationTitleParams {
}
pub async fn update_conversation_title(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<UpdateConversationTitleParams>,
) -> Result<Json<()>, AppCommandError> {
let db = app.state::<AppDatabase>();
let db = &state.db;
conversation_service::update_title(&db.conn, params.conversation_id, params.title)
.await
.map_err(AppCommandError::from)?;
@@ -195,10 +196,10 @@ pub struct DeleteConversationParams {
}
pub async fn delete_conversation(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<DeleteConversationParams>,
) -> Result<Json<()>, AppCommandError> {
let db = app.state::<AppDatabase>();
let db = &state.db;
conversation_service::soft_delete(&db.conn, params.conversation_id)
.await
.map_err(AppCommandError::from)?;
@@ -213,10 +214,10 @@ pub struct UpdateConversationExternalIdParams {
}
pub async fn update_conversation_external_id(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<UpdateConversationExternalIdParams>,
) -> Result<Json<()>, AppCommandError> {
let db = app.state::<AppDatabase>();
let db = &state.db;
conversation_service::update_external_id(&db.conn, params.conversation_id, params.external_id)
.await
.map_err(AppCommandError::from)?;

View File

@@ -1,10 +1,11 @@
use std::sync::Arc;
use axum::{extract::Extension, Json};
use serde::Deserialize;
use tauri::Manager;
use crate::app_error::AppCommandError;
use crate::app_state::AppState;
use crate::db::service::folder_command_service;
use crate::db::AppDatabase;
use crate::models::*;
// ---------------------------------------------------------------------------
@@ -52,10 +53,10 @@ pub struct ReorderFolderCommandsParams {
// ---------------------------------------------------------------------------
pub async fn list_folder_commands(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<FolderIdParams>,
) -> Result<Json<Vec<FolderCommandInfo>>, AppCommandError> {
let db = app.state::<AppDatabase>();
let db = &state.db;
let result = folder_command_service::list_by_folder(&db.conn, params.folder_id)
.await
.map_err(AppCommandError::from)?;
@@ -63,10 +64,10 @@ pub async fn list_folder_commands(
}
pub async fn create_folder_command(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<CreateFolderCommandParams>,
) -> Result<Json<FolderCommandInfo>, AppCommandError> {
let db = app.state::<AppDatabase>();
let db = &state.db;
let result = folder_command_service::create(
&db.conn,
params.folder_id,
@@ -79,10 +80,10 @@ pub async fn create_folder_command(
}
pub async fn update_folder_command(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<UpdateFolderCommandParams>,
) -> Result<Json<FolderCommandInfo>, AppCommandError> {
let db = app.state::<AppDatabase>();
let db = &state.db;
let result = folder_command_service::update(
&db.conn,
params.id,
@@ -96,10 +97,10 @@ pub async fn update_folder_command(
}
pub async fn delete_folder_command(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<DeleteFolderCommandParams>,
) -> Result<Json<()>, AppCommandError> {
let db = app.state::<AppDatabase>();
let db = &state.db;
folder_command_service::delete(&db.conn, params.id)
.await
.map_err(AppCommandError::from)?;
@@ -107,10 +108,10 @@ pub async fn delete_folder_command(
}
pub async fn reorder_folder_commands(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<ReorderFolderCommandsParams>,
) -> Result<Json<()>, AppCommandError> {
let db = app.state::<AppDatabase>();
let db = &state.db;
folder_command_service::reorder(&db.conn, params.folder_id, params.ids)
.await
.map_err(AppCommandError::from)?;
@@ -125,10 +126,10 @@ pub struct BootstrapFolderCommandsParams {
}
pub async fn bootstrap_folder_commands_from_package_json(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<BootstrapFolderCommandsParams>,
) -> Result<Json<Vec<FolderCommandInfo>>, AppCommandError> {
let db = app.state::<AppDatabase>();
let db = &state.db;
let existing = folder_command_service::list_by_folder(&db.conn, params.folder_id)
.await

View File

@@ -1,11 +1,12 @@
use std::sync::Arc;
use axum::{extract::Extension, Json};
use serde::{Deserialize, Serialize};
use tauri::Manager;
use crate::app_error::AppCommandError;
use crate::app_state::AppState;
use crate::commands::folders as folder_commands;
use crate::db::service::folder_service;
use crate::db::AppDatabase;
use crate::models::*;
#[derive(Deserialize)]
@@ -15,9 +16,9 @@ pub struct FolderIdParams {
}
pub async fn load_folder_history(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
) -> Result<Json<Vec<FolderHistoryEntry>>, AppCommandError> {
let db = app.state::<AppDatabase>();
let db = &state.db;
let result = folder_service::list_folders(&db.conn)
.await
.map_err(AppCommandError::from)?;
@@ -25,9 +26,9 @@ pub async fn load_folder_history(
}
pub async fn list_open_folders(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
) -> Result<Json<Vec<FolderHistoryEntry>>, AppCommandError> {
let db = app.state::<AppDatabase>();
let db = &state.db;
let result = folder_service::list_open_folders(&db.conn)
.await
.map_err(AppCommandError::from)?;
@@ -35,10 +36,10 @@ pub async fn list_open_folders(
}
pub async fn get_folder(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<FolderIdParams>,
) -> Result<Json<FolderDetail>, AppCommandError> {
let db = app.state::<AppDatabase>();
let db = &state.db;
let folder = folder_service::get_folder_by_id(&db.conn, params.folder_id)
.await
.map_err(AppCommandError::from)?
@@ -55,10 +56,10 @@ pub struct AddFolderParams {
/// Web equivalent of `open_folder_window`: adds the folder to DB and returns its ID.
/// The web client then navigates to `/folder?id=N` itself.
pub async fn open_folder_window(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<AddFolderParams>,
) -> Result<Json<FolderHistoryEntry>, AppCommandError> {
let db = app.state::<AppDatabase>();
let db = &state.db;
let entry = folder_service::add_folder(&db.conn, &params.path)
.await
.map_err(AppCommandError::from)?;
@@ -66,10 +67,10 @@ pub async fn open_folder_window(
}
pub async fn close_folder_window(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<FolderIdParams>,
) -> Result<Json<()>, AppCommandError> {
let db = app.state::<AppDatabase>();
let db = &state.db;
folder_service::set_folder_open(&db.conn, params.folder_id, false)
.await
.map_err(AppCommandError::from)?;
@@ -86,10 +87,10 @@ pub struct SaveFolderOpenedConversationsParams {
}
pub async fn save_folder_opened_conversations(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<SaveFolderOpenedConversationsParams>,
) -> Result<Json<()>, AppCommandError> {
let db = app.state::<AppDatabase>();
let db = &state.db;
folder_service::save_opened_conversations(&db.conn, params.folder_id, params.items)
.await
.map_err(AppCommandError::from)?;
@@ -130,10 +131,10 @@ pub struct RootPathParams {
}
pub async fn start_file_tree_watch(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<RootPathParams>,
) -> Result<Json<()>, AppCommandError> {
folder_commands::start_file_tree_watch(app, params.root_path).await?;
folder_commands::start_file_tree_watch_core(state.emitter.clone(), params.root_path).await?;
Ok(Json(()))
}
@@ -254,10 +255,10 @@ pub struct SetFolderParentBranchParams {
}
pub async fn add_folder_to_history(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<AddFolderParams>,
) -> Result<Json<FolderHistoryEntry>, AppCommandError> {
let db = app.state::<AppDatabase>();
let db = &state.db;
let result = folder_service::add_folder(&db.conn, &params.path)
.await
.map_err(AppCommandError::from)?;
@@ -265,20 +266,20 @@ pub async fn add_folder_to_history(
}
pub async fn set_folder_parent_branch(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<SetFolderParentBranchParams>,
) -> Result<Json<()>, AppCommandError> {
let db = app.state::<AppDatabase>();
let db = &state.db;
folder_commands::set_folder_parent_branch_core(&db.conn, &params.path, params.parent_branch)
.await?;
Ok(Json(()))
}
pub async fn remove_folder_from_history(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<AddFolderParams>,
) -> Result<Json<()>, AppCommandError> {
let db = app.state::<AppDatabase>();
let db = &state.db;
folder_service::remove_folder(&db.conn, &params.path)
.await
.map_err(AppCommandError::from)?;

View File

@@ -1,10 +1,11 @@
use std::sync::Arc;
use axum::{extract::Extension, Json};
use serde::Deserialize;
use tauri::Manager;
use crate::app_error::AppCommandError;
use crate::app_state::AppState;
use crate::commands::folders as folder_commands;
use crate::db::AppDatabase;
use crate::models::GitCredentials;
use super::folders::PathParams;
@@ -499,15 +500,15 @@ pub struct GitPullParams {
}
pub async fn git_pull(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<GitPullParams>,
) -> Result<Json<folder_commands::GitPullResult>, AppCommandError> {
let db = app.state::<AppDatabase>();
let db = &state.db;
let result = folder_commands::git_pull_core(
&params.path,
params.credentials.as_ref(),
&db,
&app,
db,
&state.data_dir,
)
.await?;
Ok(Json(result))
@@ -521,15 +522,15 @@ pub struct GitFetchParams {
}
pub async fn git_fetch(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<GitFetchParams>,
) -> Result<Json<String>, AppCommandError> {
let db = app.state::<AppDatabase>();
let db = &state.db;
let result = folder_commands::git_fetch_core(
&params.path,
params.credentials.as_ref(),
&db,
&app,
db,
&state.data_dir,
)
.await?;
Ok(Json(result))
@@ -545,17 +546,19 @@ pub struct GitPushParams {
}
pub async fn git_push(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<GitPushParams>,
) -> Result<Json<folder_commands::GitPushResult>, AppCommandError> {
let db = app.state::<AppDatabase>();
let db = &state.db;
let emitter = state.emitter.clone();
let result = folder_commands::git_push_core(
&app,
&state.data_dir,
&emitter,
params.folder_id,
&params.path,
params.remote.as_deref(),
params.credentials.as_ref(),
&db,
db,
)
.await?;
Ok(Json(result))
@@ -571,12 +574,13 @@ pub struct GitCommitParams {
}
pub async fn git_commit(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<GitCommitParams>,
) -> Result<Json<folder_commands::GitCommitResult>, AppCommandError> {
let db = app.state::<AppDatabase>();
let db = &state.db;
let emitter = state.emitter.clone();
let result = folder_commands::git_commit_core(
&app,
&emitter,
params.folder_id,
&db.conn,
&params.path,
@@ -596,16 +600,16 @@ pub struct GitFetchRemoteParams {
}
pub async fn git_fetch_remote(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<GitFetchRemoteParams>,
) -> Result<Json<String>, AppCommandError> {
let db = app.state::<AppDatabase>();
let db = &state.db;
let result = folder_commands::git_fetch_remote_core(
&params.path,
&params.name,
params.credentials.as_ref(),
&db,
&app,
db,
&state.data_dir,
)
.await?;
Ok(Json(result))
@@ -620,16 +624,16 @@ pub struct CloneRepositoryParams {
}
pub async fn clone_repository(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<CloneRepositoryParams>,
) -> Result<Json<()>, AppCommandError> {
let db = app.state::<AppDatabase>();
let db = &state.db;
folder_commands::clone_repository_core(
&params.url,
&params.target_dir,
params.credentials.as_ref(),
&db,
&app,
db,
&state.data_dir,
)
.await?;
Ok(Json(()))

View File

@@ -1,11 +1,12 @@
use std::sync::Arc;
use axum::{extract::Extension, Json};
use serde::Deserialize;
use tauri::Manager;
use crate::app_error::AppCommandError;
use crate::app_state::AppState;
use crate::commands::system_settings as settings_commands;
use crate::db::service::app_metadata_service;
use crate::db::AppDatabase;
use crate::models::*;
use crate::network::proxy;
@@ -32,17 +33,17 @@ pub struct UpdateLanguageSettingsParams {
// ---------------------------------------------------------------------------
pub async fn get_system_proxy_settings(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
) -> Result<Json<SystemProxySettings>, AppCommandError> {
let db = app.state::<AppDatabase>();
let db = &state.db;
let settings = settings_commands::load_system_proxy_settings(&db.conn).await?;
Ok(Json(settings))
}
pub async fn get_system_language_settings(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
) -> Result<Json<SystemLanguageSettings>, AppCommandError> {
let db = app.state::<AppDatabase>();
let db = &state.db;
let settings =
settings_commands::load_system_language_settings(&db.conn).await?;
Ok(Json(settings))
@@ -53,11 +54,11 @@ pub async fn get_system_language_settings(
// ---------------------------------------------------------------------------
pub async fn update_system_proxy_settings(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<UpdateProxySettingsParams>,
) -> Result<Json<SystemProxySettings>, AppCommandError> {
let settings = params.settings;
let db = app.state::<AppDatabase>();
let db = &state.db;
// TODO: call normalize_proxy_settings once it is made pub(crate) in
// commands/system_settings.rs. For now the frontend validates the URL.
@@ -79,11 +80,11 @@ pub async fn update_system_proxy_settings(
}
pub async fn update_system_language_settings(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<UpdateLanguageSettingsParams>,
) -> Result<Json<SystemLanguageSettings>, AppCommandError> {
let settings = params.settings;
let db = app.state::<AppDatabase>();
let db = &state.db;
let serialized = serde_json::to_string(&settings).map_err(|e| {
AppCommandError::invalid_input("Failed to serialize language settings")
@@ -99,7 +100,7 @@ pub async fn update_system_language_settings(
.map_err(AppCommandError::from)?;
crate::web::event_bridge::emit_event(
&app,
&state.emitter,
LANGUAGE_SETTINGS_UPDATED_EVENT,
settings.clone(),
);

View File

@@ -1,10 +1,12 @@
use std::sync::Arc;
use axum::{extract::Extension, Json};
use serde::Deserialize;
use tauri::Manager;
use crate::app_error::AppCommandError;
use crate::app_state::AppState;
use crate::commands::terminal::prepare_credential_env;
use crate::terminal::manager::{SpawnOptions, TerminalManager};
use crate::terminal::manager::SpawnOptions;
use crate::terminal::types::TerminalInfo;
// ---------------------------------------------------------------------------
@@ -44,18 +46,13 @@ pub struct TerminalResizeParams {
// ---------------------------------------------------------------------------
pub async fn terminal_spawn(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<TerminalSpawnParams>,
) -> Result<Json<String>, AppCommandError> {
let manager = app.state::<TerminalManager>();
let manager = &state.terminal_manager;
let terminal_id = uuid::Uuid::new_v4().to_string();
let app_data_dir = app
.path()
.app_data_dir()
.map_err(|e| AppCommandError::task_execution_failed(e.to_string()))?;
let extra_env = prepare_credential_env(&app_data_dir);
let extra_env = prepare_credential_env(&state.data_dir);
let id = manager
.spawn_with_id(
@@ -67,7 +64,7 @@ pub async fn terminal_spawn(
extra_env,
temp_files: vec![],
},
app.clone(),
state.emitter.clone(),
)
.map_err(|e| AppCommandError::task_execution_failed(e.to_string()))?;
@@ -75,10 +72,10 @@ pub async fn terminal_spawn(
}
pub async fn terminal_write(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<TerminalWriteParams>,
) -> Result<Json<()>, AppCommandError> {
let manager = app.state::<TerminalManager>();
let manager = &state.terminal_manager;
manager
.write(&params.terminal_id, params.data.as_bytes())
.map_err(|e| AppCommandError::task_execution_failed(e.to_string()))?;
@@ -86,10 +83,10 @@ pub async fn terminal_write(
}
pub async fn terminal_resize(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<TerminalResizeParams>,
) -> Result<Json<()>, AppCommandError> {
let manager = app.state::<TerminalManager>();
let manager = &state.terminal_manager;
manager
.resize(&params.terminal_id, params.cols, params.rows)
.map_err(|e| AppCommandError::task_execution_failed(e.to_string()))?;
@@ -97,10 +94,10 @@ pub async fn terminal_resize(
}
pub async fn terminal_kill(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<TerminalIdParams>,
) -> Result<Json<()>, AppCommandError> {
let manager = app.state::<TerminalManager>();
let manager = &state.terminal_manager;
manager
.kill(&params.terminal_id)
.map_err(|e| AppCommandError::task_execution_failed(e.to_string()))?;
@@ -108,9 +105,9 @@ pub async fn terminal_kill(
}
pub async fn terminal_list(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
) -> Result<Json<Vec<TerminalInfo>>, AppCommandError> {
let manager = app.state::<TerminalManager>();
let result = manager.list_with_exit_check(Some(&app));
let manager = &state.terminal_manager;
let result = manager.list_with_exit_check(Some(&state.emitter));
Ok(Json(result))
}

View File

@@ -1,11 +1,12 @@
use std::sync::Arc;
use axum::{extract::Extension, Json};
use serde::Deserialize;
use tauri::Manager;
use crate::app_error::AppCommandError;
use crate::app_state::AppState;
use crate::commands::version_control as vc_commands;
use crate::db::service::app_metadata_service;
use crate::db::AppDatabase;
use crate::models::*;
const GIT_SETTINGS_KEY: &str = "git_settings";
@@ -56,9 +57,9 @@ pub struct UpdateGitHubAccountsParams {
// ---------------------------------------------------------------------------
pub async fn detect_git(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
) -> Result<Json<GitDetectResult>, AppCommandError> {
let db = app.state::<AppDatabase>();
let db = &state.db;
let result = vc_commands::detect_git_core(&db.conn).await?;
Ok(Json(result))
}
@@ -75,9 +76,9 @@ pub async fn test_git_path(
// ---------------------------------------------------------------------------
pub async fn get_git_settings(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
) -> Result<Json<GitSettings>, AppCommandError> {
let db = app.state::<AppDatabase>();
let db = &state.db;
let raw = app_metadata_service::get_value(&db.conn, GIT_SETTINGS_KEY)
.await
.map_err(AppCommandError::from)?;
@@ -93,11 +94,11 @@ pub async fn get_git_settings(
}
pub async fn update_git_settings(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<UpdateGitSettingsParams>,
) -> Result<Json<GitSettings>, AppCommandError> {
let settings = params.settings;
let db = app.state::<AppDatabase>();
let db = &state.db;
let serialized = serde_json::to_string(&settings).map_err(|e| {
AppCommandError::invalid_input("Failed to serialize git settings")
.with_detail(e.to_string())
@@ -115,9 +116,9 @@ pub async fn update_git_settings(
// ---------------------------------------------------------------------------
pub async fn get_github_accounts(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
) -> Result<Json<GitHubAccountsSettings>, AppCommandError> {
let db = app.state::<AppDatabase>();
let db = &state.db;
let raw = app_metadata_service::get_value(&db.conn, GITHUB_ACCOUNTS_KEY)
.await
.map_err(AppCommandError::from)?;
@@ -135,11 +136,11 @@ pub async fn get_github_accounts(
}
pub async fn update_github_accounts(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<UpdateGitHubAccountsParams>,
) -> Result<Json<GitHubAccountsSettings>, AppCommandError> {
let settings = params.settings;
let db = app.state::<AppDatabase>();
let db = &state.db;
let serialized = serde_json::to_string(&settings).map_err(|e| {
AppCommandError::invalid_input("Failed to serialize GitHub accounts")
.with_detail(e.to_string())

View File

@@ -1,16 +1,16 @@
use std::sync::Arc;
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};
use crate::app_state::AppState;
use crate::web::{do_get_web_server_status, do_stop_web_server, WebServerInfo};
pub async fn get_web_server_status(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
) -> Result<Json<Option<WebServerInfo>>, AppCommandError> {
let state = app.state::<WebServerState>();
Ok(Json(do_get_web_server_status(&state)))
Ok(Json(do_get_web_server_status(&state.web_server_state)))
}
#[derive(Deserialize)]
@@ -21,19 +21,27 @@ pub struct StartWebServerParams {
}
pub async fn start_web_server(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
Json(params): Json<StartWebServerParams>,
) -> Result<Json<WebServerInfo>, AppCommandError> {
let state = app.state::<WebServerState>();
let info = do_start_web_server(&app, &state, params.port, params.host).await?;
Ok(Json(info))
// In web mode, the server is already running (this handler itself is served by it).
// This endpoint is mainly useful in Tauri mode. Return current status as a noop.
let ws = &state.web_server_state;
if ws.running.load(std::sync::atomic::Ordering::Relaxed) {
if let Some(info) = do_get_web_server_status(ws) {
return Ok(Json(info));
}
}
Err(AppCommandError::new(
crate::app_error::AppErrorCode::InvalidInput,
"Cannot start web server from within web mode",
))
}
pub async fn stop_web_server(
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
) -> Result<Json<()>, AppCommandError> {
let state = app.state::<WebServerState>();
do_stop_web_server(&state);
do_stop_web_server(&state.web_server_state);
Ok(Json(()))
}

View File

@@ -7,15 +7,15 @@ pub mod ws;
use std::net::SocketAddr;
use std::path::PathBuf;
use std::sync::atomic::{AtomicU16, Ordering};
use std::sync::Mutex;
use std::sync::{Arc, Mutex};
use serde::Serialize;
use tauri::Manager;
use crate::app_error::{AppCommandError, AppErrorCode};
use crate::app_state::AppState;
pub struct WebServerState {
handle: Mutex<Option<tauri::async_runtime::JoinHandle<()>>>,
handle: Mutex<Option<tokio::task::JoinHandle<()>>>,
port: AtomicU16,
token: Mutex<String>,
running: std::sync::atomic::AtomicBool,
@@ -40,11 +40,13 @@ pub struct WebServerInfo {
pub addresses: Vec<String>,
}
pub(crate) fn generate_random_token() -> String {
pub fn generate_random_token() -> String {
uuid::Uuid::new_v4().to_string().replace('-', "")
}
pub(crate) fn find_static_dir(app: &tauri::AppHandle) -> PathBuf {
#[cfg(feature = "tauri-runtime")]
pub(crate) fn find_static_dir_tauri(app: &tauri::AppHandle) -> PathBuf {
use tauri::Manager;
// 1. Production: bundle.resources copies out/ → web/ inside the resource directory.
let resource = app.path().resource_dir().ok();
if let Some(ref dir) = resource {
@@ -60,8 +62,11 @@ pub(crate) fn find_static_dir(app: &tauri::AppHandle) -> PathBuf {
}
}
// 2. Dev mode: "out/" is at the project root, which is one level above src-tauri/.
// The Cargo manifest dir at compile time gives us the src-tauri/ path.
find_static_dir_fallback()
}
pub(crate) fn find_static_dir_fallback() -> PathBuf {
// Dev mode: "out/" is at the project root, which is one level above src-tauri/.
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let project_out = manifest_dir.parent().map(|p| p.join("out"));
if let Some(ref out) = project_out {
@@ -71,7 +76,7 @@ pub(crate) fn find_static_dir(app: &tauri::AppHandle) -> PathBuf {
}
}
// 3. Fallback: current working directory / out
// Fallback: current working directory / out
let cwd_out = std::env::current_dir()
.map(|d| d.join("out"))
.unwrap_or_else(|_| PathBuf::from("out"));
@@ -82,7 +87,26 @@ pub(crate) fn find_static_dir(app: &tauri::AppHandle) -> PathBuf {
cwd_out
}
pub(crate) fn get_local_addresses(port: u16) -> Vec<String> {
pub fn find_static_dir_standalone(explicit: Option<&str>) -> PathBuf {
if let Some(dir) = explicit {
let p = PathBuf::from(dir);
if p.join("index.html").exists() {
eprintln!("[WEB] Serving static files from CODEG_STATIC_DIR: {}", p.display());
return p;
}
}
// Try ./web/
let web = PathBuf::from("web");
if web.join("index.html").exists() {
eprintln!("[WEB] Serving static files from ./web/: {}", web.display());
return web;
}
find_static_dir_fallback()
}
pub fn get_local_addresses(port: u16) -> Vec<String> {
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") {
@@ -98,13 +122,14 @@ pub(crate) fn get_local_addresses(port: u16) -> Vec<String> {
// ── Core logic (shared by Tauri commands and web handlers) ──
pub(crate) async fn do_start_web_server(
app: &tauri::AppHandle,
state: &WebServerState,
pub(crate) async fn do_start_web_server_with_state(
app_state: Arc<AppState>,
static_dir: PathBuf,
port: Option<u16>,
host: Option<String>,
) -> Result<WebServerInfo, AppCommandError> {
if state.running.load(Ordering::Relaxed) {
let ws = &app_state.web_server_state;
if ws.running.load(Ordering::Relaxed) {
return Err(AppCommandError::new(
AppErrorCode::AlreadyExists,
"Web server is already running",
@@ -115,8 +140,7 @@ pub(crate) async fn do_start_web_server(
let host = host.unwrap_or_else(|| "0.0.0.0".to_string());
let token = generate_random_token();
let static_dir = find_static_dir(app);
let router = router::build_router(app.clone(), token.clone(), static_dir);
let router = router::build_router(app_state.clone(), token.clone(), static_dir);
let addr: SocketAddr = format!("{}:{}", host, port)
.parse()
@@ -131,16 +155,16 @@ pub(crate) async fn do_start_web_server(
let actual_port = listener.local_addr().map(|a| a.port()).unwrap_or(port);
eprintln!("[WEB] Starting web server on {}", addr);
let handle = tauri::async_runtime::spawn(async move {
let handle = tokio::spawn(async move {
if let Err(e) = axum::serve(listener, router).await {
eprintln!("[WEB] Server error: {}", e);
}
});
*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);
*ws.handle.lock().unwrap() = Some(handle);
ws.port.store(actual_port, Ordering::Relaxed);
*ws.token.lock().unwrap() = token.clone();
ws.running.store(true, Ordering::Relaxed);
let addresses = get_local_addresses(actual_port);
Ok(WebServerInfo {
@@ -176,6 +200,7 @@ pub(crate) fn do_get_web_server_status(state: &WebServerState) -> Option<WebServ
// ── Tauri commands (thin wrappers) ──
#[cfg(feature = "tauri-runtime")]
#[tauri::command]
pub async fn start_web_server(
app: tauri::AppHandle,
@@ -183,9 +208,73 @@ pub async fn start_web_server(
port: Option<u16>,
host: Option<String>,
) -> Result<WebServerInfo, AppCommandError> {
do_start_web_server(&app, &state, port, host).await
// In Tauri mode, we still need to start via the legacy path because
// the full AppState isn't easily available from tauri::State here.
// The embedded web server uses Tauri's resource directory for static files.
use tauri::Manager;
let ws = &*state;
if ws.running.load(Ordering::Relaxed) {
return Err(AppCommandError::new(
AppErrorCode::AlreadyExists,
"Web server is already running",
));
}
let port_val = port.unwrap_or(3080);
let host_val = host.unwrap_or_else(|| "0.0.0.0".to_string());
let token = generate_random_token();
let static_dir = find_static_dir_tauri(&app);
// Build AppState for the router
let app_state = Arc::new(AppState {
db: crate::db::AppDatabase {
conn: app.state::<crate::db::AppDatabase>().conn.clone(),
},
connection_manager: (*app.state::<crate::acp::manager::ConnectionManager>()).clone_ref(),
terminal_manager: (*app.state::<crate::terminal::manager::TerminalManager>()).clone_ref(),
event_broadcaster: app.state::<Arc<crate::web::event_bridge::WebEventBroadcaster>>().inner().clone(),
emitter: crate::web::event_bridge::EventEmitter::Tauri(app.clone()),
data_dir: app.path().app_data_dir().unwrap_or_default(),
web_server_state: WebServerState::new(), // placeholder; not used by handlers
});
let router = router::build_router(app_state, token.clone(), static_dir);
let addr: SocketAddr = format!("{}:{}", host_val, port_val)
.parse()
.map_err(|e: std::net::AddrParseError| {
AppCommandError::invalid_input("Invalid host/port").with_detail(e.to_string())
})?;
let listener = tokio::net::TcpListener::bind(addr).await.map_err(|e| {
AppCommandError::io_error("Failed to bind address").with_detail(e.to_string())
})?;
let actual_port = listener.local_addr().map(|a| a.port()).unwrap_or(port_val);
eprintln!("[WEB] Starting web server on {}", addr);
let handle = tokio::spawn(async move {
if let Err(e) = axum::serve(listener, router).await {
eprintln!("[WEB] Server error: {}", e);
}
});
*ws.handle.lock().unwrap() = Some(handle);
ws.port.store(actual_port, Ordering::Relaxed);
*ws.token.lock().unwrap() = token.clone();
ws.running.store(true, Ordering::Relaxed);
let addresses = get_local_addresses(actual_port);
Ok(WebServerInfo {
port: actual_port,
token,
addresses,
})
}
#[cfg(feature = "tauri-runtime")]
#[tauri::command]
pub async fn stop_web_server(
state: tauri::State<'_, WebServerState>,
@@ -194,6 +283,7 @@ pub async fn stop_web_server(
Ok(())
}
#[cfg(feature = "tauri-runtime")]
#[tauri::command]
pub async fn get_web_server_status(
state: tauri::State<'_, WebServerState>,

View File

@@ -1,3 +1,5 @@
use std::sync::Arc;
use axum::{
extract::Extension,
http::{StatusCode, Uri},
@@ -10,8 +12,9 @@ use tower_http::cors::{Any, CorsLayer};
use tower_http::services::{ServeDir, ServeFile};
use super::{auth, handlers, ws};
use crate::app_state::AppState;
pub fn build_router(app: tauri::AppHandle, token: String, static_dir: std::path::PathBuf) -> Router {
pub fn build_router(state: Arc<AppState>, token: String, static_dir: std::path::PathBuf) -> Router {
let cors = CorsLayer::new()
.allow_origin(Any)
.allow_methods(Any)
@@ -234,7 +237,7 @@ pub fn build_router(app: tauri::AppHandle, token: String, static_dir: std::path:
.fallback_service(fallback)
.layer(html_rewrite)
.layer(cors)
.layer(Extension(app))
.layer(Extension(state))
}
async fn health_check() -> impl IntoResponse {

View File

@@ -1,22 +1,22 @@
use std::sync::Arc;
use axum::{
extract::{Extension, WebSocketUpgrade},
response::IntoResponse,
};
use axum::extract::ws::{Message, WebSocket};
use tauri::Manager;
use super::event_bridge::WebEventBroadcaster;
use crate::app_state::AppState;
pub async fn ws_handler(
ws: WebSocketUpgrade,
Extension(app): Extension<tauri::AppHandle>,
Extension(state): Extension<Arc<AppState>>,
) -> impl IntoResponse {
ws.on_upgrade(|socket| handle_ws_connection(socket, app))
ws.on_upgrade(|socket| handle_ws_connection(socket, state))
}
async fn handle_ws_connection(mut socket: WebSocket, app: tauri::AppHandle) {
let broadcaster = app.state::<WebEventBroadcaster>();
let mut rx = broadcaster.subscribe();
async fn handle_ws_connection(mut socket: WebSocket, state: Arc<AppState>) {
let mut rx = state.event_broadcaster.subscribe();
loop {
tokio::select! {