支持无GUI的Server运行模式
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(¶ms.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(¶ms.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(¶ms.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(¶ms.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(¶ms.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(¶ms.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(¶ms.connection_id, ¶ms.request_id, ¶ms.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(¶ms.agent_types, &db, &app)
|
||||
let db = &state.db;
|
||||
let emitter = state.emitter.clone();
|
||||
acp_commands::acp_reorder_agents_core(¶ms.agent_types, &db, &emitter)
|
||||
.await
|
||||
.map_err(|e| AppCommandError::task_execution_failed(e.to_string()))?;
|
||||
Ok(Json(()))
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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, ¶ms.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, ¶ms.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, ¶ms.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, ¶ms.path)
|
||||
.await
|
||||
.map_err(AppCommandError::from)?;
|
||||
|
||||
@@ -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(
|
||||
¶ms.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(
|
||||
¶ms.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,
|
||||
¶ms.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,
|
||||
¶ms.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(
|
||||
¶ms.path,
|
||||
¶ms.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(
|
||||
¶ms.url,
|
||||
¶ms.target_dir,
|
||||
params.credentials.as_ref(),
|
||||
&db,
|
||||
&app,
|
||||
db,
|
||||
&state.data_dir,
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(()))
|
||||
|
||||
@@ -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(),
|
||||
);
|
||||
|
||||
@@ -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(¶ms.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(¶ms.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(¶ms.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))
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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(()))
|
||||
}
|
||||
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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! {
|
||||
|
||||
Reference in New Issue
Block a user