commands/web结构性优化
This commit is contained in:
@@ -246,12 +246,13 @@ pub async fn import_local_conversations(
|
|||||||
.map_err(AppCommandError::from)
|
.map_err(AppCommandError::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
/// Core logic for loading a folder conversation with full OpenClaw fallback.
|
||||||
pub async fn get_folder_conversation(
|
/// Shared by both the Tauri command and the web handler.
|
||||||
db: tauri::State<'_, AppDatabase>,
|
pub async fn get_folder_conversation_core(
|
||||||
|
conn: &sea_orm::DatabaseConnection,
|
||||||
conversation_id: i32,
|
conversation_id: i32,
|
||||||
) -> Result<DbConversationDetail, AppCommandError> {
|
) -> Result<DbConversationDetail, AppCommandError> {
|
||||||
let summary = conversation_service::get_by_id(&db.conn, conversation_id)
|
let summary = conversation_service::get_by_id(conn, conversation_id)
|
||||||
.await
|
.await
|
||||||
.map_err(AppCommandError::from)?;
|
.map_err(AppCommandError::from)?;
|
||||||
|
|
||||||
@@ -261,7 +262,7 @@ pub async fn get_folder_conversation(
|
|||||||
let eid = ext_id.clone();
|
let eid = ext_id.clone();
|
||||||
let db_created_at = summary.created_at;
|
let db_created_at = summary.created_at;
|
||||||
let folder_path_for_fallback = {
|
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
|
.await
|
||||||
.ok()
|
.ok()
|
||||||
.flatten();
|
.flatten();
|
||||||
@@ -332,7 +333,7 @@ pub async fn get_folder_conversation(
|
|||||||
// update the database so future lookups are direct.
|
// update the database so future lookups are direct.
|
||||||
if let Some(new_ext_id) = resolved_ext_id {
|
if let Some(new_ext_id) = resolved_ext_id {
|
||||||
let _ =
|
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;
|
let mut summary = summary;
|
||||||
@@ -346,14 +347,22 @@ pub async fn get_folder_conversation(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn create_conversation(
|
pub async fn get_folder_conversation(
|
||||||
db: tauri::State<'_, AppDatabase>,
|
db: tauri::State<'_, AppDatabase>,
|
||||||
|
conversation_id: i32,
|
||||||
|
) -> Result<DbConversationDetail, AppCommandError> {
|
||||||
|
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,
|
folder_id: i32,
|
||||||
agent_type: AgentType,
|
agent_type: AgentType,
|
||||||
title: Option<String>,
|
title: Option<String>,
|
||||||
) -> Result<i32, AppCommandError> {
|
) -> Result<i32, AppCommandError> {
|
||||||
// Detect current git branch from the folder path
|
let git_branch = if let Some(folder) = folder_service::get_folder_by_id(conn, folder_id)
|
||||||
let git_branch = if let Some(folder) = folder_service::get_folder_by_id(&db.conn, folder_id)
|
|
||||||
.await
|
.await
|
||||||
.map_err(AppCommandError::from)?
|
.map_err(AppCommandError::from)?
|
||||||
{
|
{
|
||||||
@@ -362,12 +371,22 @@ pub async fn create_conversation(
|
|||||||
None
|
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
|
.await
|
||||||
.map_err(AppCommandError::from)?;
|
.map_err(AppCommandError::from)?;
|
||||||
Ok(model.id)
|
Ok(model.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn create_conversation(
|
||||||
|
db: tauri::State<'_, AppDatabase>,
|
||||||
|
folder_id: i32,
|
||||||
|
agent_type: AgentType,
|
||||||
|
title: Option<String>,
|
||||||
|
) -> Result<i32, AppCommandError> {
|
||||||
|
create_conversation_core(&db.conn, folder_id, agent_type, title).await
|
||||||
|
}
|
||||||
|
|
||||||
async fn detect_git_branch(path: &str) -> Option<String> {
|
async fn detect_git_branch(path: &str) -> Option<String> {
|
||||||
let output = crate::process::tokio_command("git")
|
let output = crate::process::tokio_command("git")
|
||||||
.args(["rev-parse", "--abbrev-ref", "HEAD"])
|
.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<AgentType>,
|
|
||||||
search: Option<String>,
|
|
||||||
sort_by: Option<String>,
|
|
||||||
folder_path: Option<String>,
|
|
||||||
) -> Result<Vec<ConversationSummary>, 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<Vec<FolderInfo>, 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<AgentStats, AppCommandError> {
|
|
||||||
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<SidebarData, AppCommandError> {
|
|
||||||
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 {
|
fn parse_error_to_app_error(error: ParseError) -> AppCommandError {
|
||||||
match error {
|
match error {
|
||||||
ParseError::ConversationNotFound(id) => {
|
ParseError::ConversationNotFound(id) => {
|
||||||
|
|||||||
@@ -5,9 +5,6 @@ use axum::{
|
|||||||
response::{IntoResponse, Response},
|
response::{IntoResponse, Response},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct AuthToken(pub String);
|
|
||||||
|
|
||||||
pub async fn require_token(
|
pub async fn require_token(
|
||||||
request: Request,
|
request: Request,
|
||||||
next: Next,
|
next: Next,
|
||||||
|
|||||||
@@ -32,10 +32,6 @@ impl WebEventBroadcaster {
|
|||||||
pub fn subscribe(&self) -> broadcast::Receiver<WebEvent> {
|
pub fn subscribe(&self) -> broadcast::Receiver<WebEvent> {
|
||||||
self.sender.subscribe()
|
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.
|
/// Unified event emission: sends to both Tauri webview and Web clients.
|
||||||
|
|||||||
@@ -3,15 +3,10 @@ use serde::Deserialize;
|
|||||||
use tauri::Manager;
|
use tauri::Manager;
|
||||||
|
|
||||||
use crate::app_error::AppCommandError;
|
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::service::{conversation_service, folder_service, import_service};
|
||||||
use crate::db::AppDatabase;
|
use crate::db::AppDatabase;
|
||||||
use crate::models::*;
|
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)]
|
#[derive(Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
@@ -53,7 +48,7 @@ pub struct ListConversationsParams {
|
|||||||
pub async fn list_conversations(
|
pub async fn list_conversations(
|
||||||
Json(params): Json<ListConversationsParams>,
|
Json(params): Json<ListConversationsParams>,
|
||||||
) -> Result<Json<Vec<ConversationSummary>>, AppCommandError> {
|
) -> Result<Json<Vec<ConversationSummary>>, AppCommandError> {
|
||||||
let result = crate::commands::conversations::list_conversations_for_web(
|
let result = conv_commands::list_conversations(
|
||||||
params.agent_type,
|
params.agent_type,
|
||||||
params.search,
|
params.search,
|
||||||
params.sort_by,
|
params.sort_by,
|
||||||
@@ -73,25 +68,8 @@ pub struct GetConversationParams {
|
|||||||
pub async fn get_conversation(
|
pub async fn get_conversation(
|
||||||
Json(params): Json<GetConversationParams>,
|
Json(params): Json<GetConversationParams>,
|
||||||
) -> Result<Json<ConversationDetail>, AppCommandError> {
|
) -> Result<Json<ConversationDetail>, AppCommandError> {
|
||||||
let at = params.agent_type;
|
let result =
|
||||||
let cid = params.conversation_id;
|
conv_commands::get_conversation(params.agent_type, params.conversation_id).await?;
|
||||||
let result = tokio::task::spawn_blocking(move || -> Result<ConversationDetail, AppCommandError> {
|
|
||||||
let parser: Box<dyn AgentParser> = 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())
|
|
||||||
})??;
|
|
||||||
Ok(Json(result))
|
Ok(Json(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,57 +84,23 @@ pub async fn get_folder_conversation(
|
|||||||
Json(params): Json<GetFolderConversationParams>,
|
Json(params): Json<GetFolderConversationParams>,
|
||||||
) -> Result<Json<DbConversationDetail>, AppCommandError> {
|
) -> Result<Json<DbConversationDetail>, AppCommandError> {
|
||||||
let db = app.state::<AppDatabase>();
|
let db = app.state::<AppDatabase>();
|
||||||
let summary = conversation_service::get_by_id(&db.conn, params.conversation_id)
|
let result =
|
||||||
.await
|
conv_commands::get_folder_conversation_core(&db.conn, params.conversation_id).await?;
|
||||||
.map_err(AppCommandError::from)?;
|
Ok(Json(result))
|
||||||
|
|
||||||
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<dyn AgentParser> = 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::<String>)),
|
|
||||||
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,
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn list_folders() -> Result<Json<Vec<FolderInfo>>, AppCommandError> {
|
pub async fn list_folders() -> Result<Json<Vec<FolderInfo>>, AppCommandError> {
|
||||||
let result = crate::commands::conversations::list_folders_for_web().await?;
|
let result = conv_commands::list_folders().await?;
|
||||||
Ok(Json(result))
|
Ok(Json(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_stats() -> Result<Json<AgentStats>, AppCommandError> {
|
pub async fn get_stats() -> Result<Json<AgentStats>, AppCommandError> {
|
||||||
let result = crate::commands::conversations::get_stats_for_web().await?;
|
let result = conv_commands::get_stats().await?;
|
||||||
Ok(Json(result))
|
Ok(Json(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_sidebar_data() -> Result<Json<SidebarData>, AppCommandError> {
|
pub async fn get_sidebar_data() -> Result<Json<SidebarData>, AppCommandError> {
|
||||||
let result = crate::commands::conversations::get_sidebar_data_for_web().await?;
|
let result = conv_commands::get_sidebar_data().await?;
|
||||||
Ok(Json(result))
|
Ok(Json(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,16 +138,14 @@ pub async fn create_conversation(
|
|||||||
Json(params): Json<CreateConversationParams>,
|
Json(params): Json<CreateConversationParams>,
|
||||||
) -> Result<Json<i32>, AppCommandError> {
|
) -> Result<Json<i32>, AppCommandError> {
|
||||||
let db = app.state::<AppDatabase>();
|
let db = app.state::<AppDatabase>();
|
||||||
let model = conversation_service::create(
|
let result = conv_commands::create_conversation_core(
|
||||||
&db.conn,
|
&db.conn,
|
||||||
params.folder_id,
|
params.folder_id,
|
||||||
params.agent_type,
|
params.agent_type,
|
||||||
params.title,
|
params.title,
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(AppCommandError::from)?;
|
Ok(Json(result))
|
||||||
Ok(Json(model.id))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
|||||||
Reference in New Issue
Block a user