diff --git a/src-tauri/src/commands/conversations.rs b/src-tauri/src/commands/conversations.rs index 8f26c44..4a72a27 100644 --- a/src-tauri/src/commands/conversations.rs +++ b/src-tauri/src/commands/conversations.rs @@ -246,12 +246,13 @@ pub async fn import_local_conversations( .map_err(AppCommandError::from) } -#[tauri::command] -pub async fn get_folder_conversation( - db: tauri::State<'_, AppDatabase>, +/// Core logic for loading a folder conversation with full OpenClaw fallback. +/// Shared by both the Tauri command and the web handler. +pub async fn get_folder_conversation_core( + conn: &sea_orm::DatabaseConnection, conversation_id: i32, ) -> Result { - let summary = conversation_service::get_by_id(&db.conn, conversation_id) + let summary = conversation_service::get_by_id(conn, conversation_id) .await .map_err(AppCommandError::from)?; @@ -261,7 +262,7 @@ pub async fn get_folder_conversation( let eid = ext_id.clone(); let db_created_at = summary.created_at; let folder_path_for_fallback = { - let folder = folder_service::get_folder_by_id(&db.conn, summary.folder_id) + let folder = folder_service::get_folder_by_id(conn, summary.folder_id) .await .ok() .flatten(); @@ -332,7 +333,7 @@ pub async fn get_folder_conversation( // update the database so future lookups are direct. if let Some(new_ext_id) = resolved_ext_id { let _ = - conversation_service::update_external_id(&db.conn, conversation_id, new_ext_id).await; + conversation_service::update_external_id(conn, conversation_id, new_ext_id).await; } let mut summary = summary; @@ -346,14 +347,22 @@ pub async fn get_folder_conversation( } #[tauri::command] -pub async fn create_conversation( +pub async fn get_folder_conversation( db: tauri::State<'_, AppDatabase>, + conversation_id: i32, +) -> Result { + get_folder_conversation_core(&db.conn, conversation_id).await +} + +/// Core logic for creating a conversation with git branch detection. +/// Shared by both the Tauri command and the web handler. +pub async fn create_conversation_core( + conn: &sea_orm::DatabaseConnection, folder_id: i32, agent_type: AgentType, title: Option, ) -> Result { - // Detect current git branch from the folder path - let git_branch = if let Some(folder) = folder_service::get_folder_by_id(&db.conn, folder_id) + let git_branch = if let Some(folder) = folder_service::get_folder_by_id(conn, folder_id) .await .map_err(AppCommandError::from)? { @@ -362,12 +371,22 @@ pub async fn create_conversation( None }; - let model = conversation_service::create(&db.conn, folder_id, agent_type, title, git_branch) + let model = conversation_service::create(conn, folder_id, agent_type, title, git_branch) .await .map_err(AppCommandError::from)?; Ok(model.id) } +#[tauri::command] +pub async fn create_conversation( + db: tauri::State<'_, AppDatabase>, + folder_id: i32, + agent_type: AgentType, + title: Option, +) -> Result { + create_conversation_core(&db.conn, folder_id, agent_type, title).await +} + async fn detect_git_branch(path: &str) -> Option { let output = crate::process::tokio_command("git") .args(["rev-parse", "--abbrev-ref", "HEAD"]) @@ -459,60 +478,6 @@ fn compute_stats(all_conversations: &[ConversationSummary]) -> AgentStats { } } -// ── Public helpers for the embedded web server ── - -pub async fn list_conversations_for_web( - agent_type: Option, - search: Option, - sort_by: Option, - folder_path: Option, -) -> Result, AppCommandError> { - tokio::task::spawn_blocking(move || list_conversations_sync(agent_type, search, sort_by, folder_path)) - .await - .map_err(|e| { - AppCommandError::task_execution_failed("Failed to list conversations") - .with_detail(e.to_string()) - }) -} - -pub async fn list_folders_for_web() -> Result, AppCommandError> { - tokio::task::spawn_blocking(move || { - let all = list_conversations_sync(None, None, None, None); - compute_folders(&all) - }) - .await - .map_err(|e| { - AppCommandError::task_execution_failed("Failed to list folders").with_detail(e.to_string()) - }) -} - -pub async fn get_stats_for_web() -> Result { - tokio::task::spawn_blocking(move || { - let all = list_conversations_sync(None, None, None, None); - compute_stats(&all) - }) - .await - .map_err(|e| { - AppCommandError::task_execution_failed("Failed to compute stats") - .with_detail(e.to_string()) - }) -} - -pub async fn get_sidebar_data_for_web() -> Result { - tokio::task::spawn_blocking(move || { - let all = list_conversations_sync(None, None, None, None); - SidebarData { - folders: compute_folders(&all), - stats: compute_stats(&all), - } - }) - .await - .map_err(|e| { - AppCommandError::task_execution_failed("Failed to build sidebar data") - .with_detail(e.to_string()) - }) -} - fn parse_error_to_app_error(error: ParseError) -> AppCommandError { match error { ParseError::ConversationNotFound(id) => { diff --git a/src-tauri/src/web/auth.rs b/src-tauri/src/web/auth.rs index 89dc030..3b9afdf 100644 --- a/src-tauri/src/web/auth.rs +++ b/src-tauri/src/web/auth.rs @@ -5,9 +5,6 @@ use axum::{ response::{IntoResponse, Response}, }; -#[derive(Clone)] -pub struct AuthToken(pub String); - pub async fn require_token( request: Request, next: Next, diff --git a/src-tauri/src/web/event_bridge.rs b/src-tauri/src/web/event_bridge.rs index a538096..0fa35d8 100644 --- a/src-tauri/src/web/event_bridge.rs +++ b/src-tauri/src/web/event_bridge.rs @@ -32,10 +32,6 @@ impl WebEventBroadcaster { pub fn subscribe(&self) -> broadcast::Receiver { self.sender.subscribe() } - - pub fn has_subscribers(&self) -> bool { - self.sender.receiver_count() > 0 - } } /// Unified event emission: sends to both Tauri webview and Web clients. diff --git a/src-tauri/src/web/handlers/conversations.rs b/src-tauri/src/web/handlers/conversations.rs index 0c5e8a0..a70558c 100644 --- a/src-tauri/src/web/handlers/conversations.rs +++ b/src-tauri/src/web/handlers/conversations.rs @@ -3,15 +3,10 @@ use serde::Deserialize; use tauri::Manager; use crate::app_error::AppCommandError; +use crate::commands::conversations as conv_commands; use crate::db::service::{conversation_service, folder_service, import_service}; use crate::db::AppDatabase; use crate::models::*; -use crate::parsers::claude::ClaudeParser; -use crate::parsers::codex::CodexParser; -use crate::parsers::gemini::GeminiParser; -use crate::parsers::openclaw::OpenClawParser; -use crate::parsers::opencode::OpenCodeParser; -use crate::parsers::AgentParser; #[derive(Deserialize)] #[serde(rename_all = "camelCase")] @@ -53,7 +48,7 @@ pub struct ListConversationsParams { pub async fn list_conversations( Json(params): Json, ) -> Result>, AppCommandError> { - let result = crate::commands::conversations::list_conversations_for_web( + let result = conv_commands::list_conversations( params.agent_type, params.search, params.sort_by, @@ -73,25 +68,8 @@ pub struct GetConversationParams { pub async fn get_conversation( Json(params): Json, ) -> Result, AppCommandError> { - let at = params.agent_type; - let cid = params.conversation_id; - let result = tokio::task::spawn_blocking(move || -> Result { - let parser: Box = match at { - AgentType::ClaudeCode => Box::new(ClaudeParser::new()), - AgentType::Codex => Box::new(CodexParser::new()), - AgentType::OpenCode => Box::new(OpenCodeParser::new()), - AgentType::Gemini => Box::new(GeminiParser::new()), - AgentType::OpenClaw => Box::new(OpenClawParser::new()), - }; - parser - .get_conversation(&cid) - .map_err(|e| AppCommandError::not_found("Conversation not found").with_detail(e.to_string())) - }) - .await - .map_err(|e| { - AppCommandError::task_execution_failed("Failed to load conversation") - .with_detail(e.to_string()) - })??; + let result = + conv_commands::get_conversation(params.agent_type, params.conversation_id).await?; Ok(Json(result)) } @@ -106,57 +84,23 @@ pub async fn get_folder_conversation( Json(params): Json, ) -> Result, AppCommandError> { let db = app.state::(); - let summary = conversation_service::get_by_id(&db.conn, params.conversation_id) - .await - .map_err(AppCommandError::from)?; - - let (turns, session_stats, _resolved_ext_id) = if let Some(ref ext_id) = summary.external_id { - let at = summary.agent_type; - let eid = ext_id.clone(); - tokio::task::spawn_blocking(move || -> Result<_, AppCommandError> { - let parser: Box = match at { - AgentType::ClaudeCode => Box::new(ClaudeParser::new()), - AgentType::Codex => Box::new(CodexParser::new()), - AgentType::OpenCode => Box::new(OpenCodeParser::new()), - AgentType::Gemini => Box::new(GeminiParser::new()), - AgentType::OpenClaw => Box::new(OpenClawParser::new()), - }; - match parser.get_conversation(&eid) { - Ok(d) => Ok((d.turns, d.session_stats, None::)), - Err(_) => Ok((vec![], None, None)), - } - }) - .await - .map_err(|e| { - AppCommandError::task_execution_failed("Failed to read conversation turns") - .with_detail(e.to_string()) - })?? - } else { - (vec![], None, None) - }; - - let mut summary = summary; - summary.message_count = turns.len() as u32; - - Ok(Json(DbConversationDetail { - summary, - turns, - session_stats, - })) + let result = + conv_commands::get_folder_conversation_core(&db.conn, params.conversation_id).await?; + Ok(Json(result)) } pub async fn list_folders() -> Result>, AppCommandError> { - let result = crate::commands::conversations::list_folders_for_web().await?; + let result = conv_commands::list_folders().await?; Ok(Json(result)) } pub async fn get_stats() -> Result, AppCommandError> { - let result = crate::commands::conversations::get_stats_for_web().await?; + let result = conv_commands::get_stats().await?; Ok(Json(result)) } pub async fn get_sidebar_data() -> Result, AppCommandError> { - let result = crate::commands::conversations::get_sidebar_data_for_web().await?; + let result = conv_commands::get_sidebar_data().await?; Ok(Json(result)) } @@ -194,16 +138,14 @@ pub async fn create_conversation( Json(params): Json, ) -> Result, AppCommandError> { let db = app.state::(); - let model = conversation_service::create( + let result = conv_commands::create_conversation_core( &db.conn, params.folder_id, params.agent_type, params.title, - None, ) - .await - .map_err(AppCommandError::from)?; - Ok(Json(model.id)) + .await?; + Ok(Json(result)) } #[derive(Deserialize)]