folders.rs多语言处理

This commit is contained in:
xintaofei
2026-03-07 15:59:14 +08:00
parent 6e5219cc10
commit 349845c137
8 changed files with 105 additions and 99 deletions

View File

@@ -136,13 +136,11 @@ pub async fn get_conversation(
AgentType::OpenCode => Box::new(OpenCodeParser::new()),
AgentType::Gemini => Box::new(GeminiParser::new()),
_ => {
return Err(
AppCommandError::new(
AppErrorCode::InvalidInput,
"Conversation parsing is not supported for this agent",
)
.with_detail(format!("agent_type={agent_type}")),
return Err(AppCommandError::new(
AppErrorCode::InvalidInput,
"Conversation parsing is not supported for this agent",
)
.with_detail(format!("agent_type={agent_type}")))
}
};
@@ -178,8 +176,11 @@ pub async fn get_stats() -> Result<AgentStats, AppCommandError> {
})
.await
.map_err(|e| {
AppCommandError::new(AppErrorCode::Unknown, "Failed to compute conversation stats")
.with_detail(e.to_string())
AppCommandError::new(
AppErrorCode::Unknown,
"Failed to compute conversation stats",
)
.with_detail(e.to_string())
})?
}
@@ -347,13 +348,11 @@ pub async fn update_conversation_status(
conversation_id: i32,
status: String,
) -> Result<(), AppCommandError> {
let status_enum: conversation::ConversationStatus = serde_json::from_value(
serde_json::Value::String(status),
)
.map_err(|e| {
AppCommandError::new(AppErrorCode::InvalidInput, "Invalid conversation status")
.with_detail(e.to_string())
})?;
let status_enum: conversation::ConversationStatus =
serde_json::from_value(serde_json::Value::String(status)).map_err(|e| {
AppCommandError::new(AppErrorCode::InvalidInput, "Invalid conversation status")
.with_detail(e.to_string())
})?;
conversation_service::update_status(&db.conn, conversation_id, status_enum)
.await
.map_err(AppCommandError::from)
@@ -419,8 +418,7 @@ fn compute_stats(all_conversations: &[ConversationSummary]) -> AgentStats {
fn parse_error_to_app_error(error: ParseError) -> AppCommandError {
match error {
ParseError::ConversationNotFound(id) => {
AppCommandError::new(AppErrorCode::NotFound, "Conversation not found")
.with_detail(id)
AppCommandError::new(AppErrorCode::NotFound, "Conversation not found").with_detail(id)
}
ParseError::InvalidData(message) => {
AppCommandError::new(AppErrorCode::InvalidInput, "Invalid conversation data")
@@ -428,10 +426,11 @@ fn parse_error_to_app_error(error: ParseError) -> AppCommandError {
}
ParseError::Io(err) => AppCommandError::new(AppErrorCode::IoError, "I/O operation failed")
.with_detail(err.to_string()),
ParseError::Json(err) => {
AppCommandError::new(AppErrorCode::InvalidInput, "Failed to parse conversation file")
.with_detail(err.to_string())
}
ParseError::Json(err) => AppCommandError::new(
AppErrorCode::InvalidInput,
"Failed to parse conversation file",
)
.with_detail(err.to_string()),
ParseError::Db(err) => {
AppCommandError::new(AppErrorCode::DatabaseError, "Database operation failed")
.with_detail(err.to_string())

View File

@@ -305,7 +305,7 @@ pub async fn set_folder_parent_branch(
db: tauri::State<'_, AppDatabase>,
path: String,
parent_branch: Option<String>,
) -> Result<(), String> {
) -> Result<(), AppCommandError> {
// Find folder by path first
use crate::db::entities::folder;
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
@@ -314,12 +314,15 @@ pub async fn set_folder_parent_branch(
.filter(folder::Column::DeletedAt.is_null())
.one(&db.conn)
.await
.map_err(|e| e.to_string())?;
.map_err(|e| {
AppCommandError::new(AppErrorCode::DatabaseError, "Failed to query folder")
.with_detail(e.to_string())
})?;
if let Some(folder_model) = row {
folder_service::set_folder_parent_branch(&db.conn, folder_model.id, parent_branch)
.await
.map_err(|e| e.to_string())?;
.map_err(AppCommandError::from)?;
}
Ok(())
}
@@ -606,11 +609,10 @@ pub async fn git_worktree_add(
.await
.map_err(AppCommandError::io)?;
if check.status.success() {
return Err(AppCommandError::new(
AppErrorCode::AlreadyExists,
"Branch already exists",
)
.with_detail(branch_name));
return Err(
AppCommandError::new(AppErrorCode::AlreadyExists, "Branch already exists")
.with_detail(branch_name),
);
}
// 校验目录是否已存在
@@ -810,10 +812,10 @@ pub async fn git_diff_with_branch(
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
return Err(
AppCommandError::external_command("git diff failed", stderr)
.with_detail(format!("branch={target_branch}")),
);
return Err(AppCommandError::external_command(
"git diff failed",
format!("branch={target_branch}; {stderr}"),
));
}
Ok(String::from_utf8_lossy(&output.stdout).to_string())
@@ -1104,7 +1106,10 @@ pub async fn git_list_all_branches(path: String) -> Result<GitBranchList, AppCom
}
#[tauri::command]
pub async fn git_merge(path: String, branch_name: String) -> Result<GitMergeResult, AppCommandError> {
pub async fn git_merge(
path: String,
branch_name: String,
) -> Result<GitMergeResult, AppCommandError> {
// Count commits to be merged before performing merge
let count_output = crate::process::tokio_command("git")
.args(["rev-list", "--count", &format!("HEAD..{}", branch_name)])
@@ -1289,9 +1294,11 @@ fn should_refresh_git_status_for_paths(root_display: &str, changed_paths: &[Stri
.any(|path| !ignored.contains(path.as_str()))
}
fn canonicalize_watch_root(root: &Path) -> Result<(PathBuf, String), String> {
let canonical = std::fs::canonicalize(root)
.map_err(|e| format!("Unable to resolve workspace root: {e}"))?;
fn canonicalize_watch_root(root: &Path) -> Result<(PathBuf, String), AppCommandError> {
let canonical = std::fs::canonicalize(root).map_err(|e| {
AppCommandError::new(AppErrorCode::NotFound, "Unable to resolve workspace root")
.with_detail(e.to_string())
})?;
let key = normalize_slash_path(&canonical);
Ok((canonical, key))
}
@@ -1560,18 +1567,27 @@ fn validate_new_name(new_name: &str) -> Result<&str, String> {
}
#[tauri::command]
pub async fn start_file_tree_watch(app: tauri::AppHandle, root_path: String) -> Result<(), String> {
pub async fn start_file_tree_watch(
app: tauri::AppHandle,
root_path: String,
) -> Result<(), AppCommandError> {
let root = PathBuf::from(&root_path);
if !root.exists() || !root.is_dir() {
return Err("Folder does not exist".to_string());
return Err(AppCommandError::new(
AppErrorCode::NotFound,
"Folder does not exist",
));
}
let (root_canonical, key) = canonicalize_watch_root(&root)?;
{
let mut watchers = FILE_WATCHERS
.lock()
.map_err(|_| "Failed to lock file watcher registry".to_string())?;
let mut watchers = FILE_WATCHERS.lock().map_err(|_| {
AppCommandError::new(
AppErrorCode::Unknown,
"Failed to lock file watcher registry",
)
})?;
if let Some(entry) = watchers.get_mut(&key) {
entry.ref_count += 1;
return Ok(());
@@ -1606,19 +1622,30 @@ pub async fn start_file_tree_watch(app: tauri::AppHandle, root_path: String) ->
}
},
)
.map_err(|e| format!("Failed to create file watcher: {e}"))?,
.map_err(|e| {
AppCommandError::new(AppErrorCode::IoError, "Failed to create file watcher")
.with_detail(e.to_string())
})?,
);
watcher
.as_mut()
.ok_or_else(|| "Failed to create file watcher".to_string())?
.ok_or_else(|| {
AppCommandError::new(AppErrorCode::Unknown, "Failed to create file watcher")
})?
.watch(&root_canonical, RecursiveMode::Recursive)
.map_err(|e| format!("Failed to start file watcher: {e}"))?;
.map_err(|e| {
AppCommandError::new(AppErrorCode::IoError, "Failed to start file watcher")
.with_detail(e.to_string())
})?;
let should_cleanup_new_watcher = {
let mut watchers = FILE_WATCHERS
.lock()
.map_err(|_| "Failed to lock file watcher registry".to_string())?;
let mut watchers = FILE_WATCHERS.lock().map_err(|_| {
AppCommandError::new(
AppErrorCode::Unknown,
"Failed to lock file watcher registry",
)
})?;
if let Some(entry) = watchers.get_mut(&key) {
entry.ref_count += 1;
true
@@ -1628,9 +1655,12 @@ pub async fn start_file_tree_watch(app: tauri::AppHandle, root_path: String) ->
FileWatchEntry {
root_canonical,
root_display: root_path,
watcher: watcher
.take()
.ok_or_else(|| "Failed to initialize file watcher state".to_string())?,
watcher: watcher.take().ok_or_else(|| {
AppCommandError::new(
AppErrorCode::Unknown,
"Failed to initialize file watcher state",
)
})?,
worker: worker.take(),
ref_count: 1,
},
@@ -1652,15 +1682,18 @@ pub async fn start_file_tree_watch(app: tauri::AppHandle, root_path: String) ->
}
#[tauri::command]
pub async fn stop_file_tree_watch(root_path: String) -> Result<(), String> {
pub async fn stop_file_tree_watch(root_path: String) -> Result<(), AppCommandError> {
let root = PathBuf::from(&root_path);
let key = canonicalize_watch_root(&root)
.map(|(_, key)| key)
.unwrap_or_else(|_| normalize_slash_path(&root));
let mut watchers = FILE_WATCHERS
.lock()
.map_err(|_| "Failed to lock file watcher registry".to_string())?;
let mut watchers = FILE_WATCHERS.lock().map_err(|_| {
AppCommandError::new(
AppErrorCode::Unknown,
"Failed to lock file watcher registry",
)
})?;
let target_key = if watchers.contains_key(&key) {
Some(key)

View File

@@ -191,13 +191,11 @@ pub async fn focus_folder_window(app: AppHandle, folder_id: i32) -> Result<(), A
}
}
}
Err(
AppCommandError::new(
AppErrorCode::NotFound,
format!("No open window for folder {folder_id}"),
)
.with_detail(format!("folder_id={folder_id}")),
Err(AppCommandError::new(
AppErrorCode::NotFound,
format!("No open window for folder {folder_id}"),
)
.with_detail(format!("folder_id={folder_id}")))
}
#[tauri::command]
@@ -251,9 +249,9 @@ pub async fn open_commit_window(
}
state.set_owner(label.clone(), owner_label);
let _ = existing.unminimize();
existing.set_focus().map_err(|e| {
AppCommandError::window("Failed to focus commit window", e.to_string())
})?;
existing
.set_focus()
.map_err(|e| AppCommandError::window("Failed to focus commit window", e.to_string()))?;
return Ok(());
}

View File

@@ -286,10 +286,7 @@ impl ClaudeParser {
}
fn resolve_claude_config_dir() -> PathBuf {
resolve_claude_config_dir_from(
std::env::var_os("CLAUDE_CONFIG_DIR"),
dirs::home_dir(),
)
resolve_claude_config_dir_from(std::env::var_os("CLAUDE_CONFIG_DIR"), dirs::home_dir())
}
fn resolve_claude_config_dir_from(
@@ -974,10 +971,7 @@ mod tests {
#[test]
fn claude_config_dir_defaults_to_home_dot_claude() {
let resolved = resolve_claude_config_dir_from(
None,
Some(PathBuf::from("/Users/default")),
);
let resolved = resolve_claude_config_dir_from(None, Some(PathBuf::from("/Users/default")));
assert_eq!(resolved, PathBuf::from("/Users/default/.claude"));
}
}

View File

@@ -162,10 +162,7 @@ impl CodexParser {
}
fn resolve_codex_home_dir() -> PathBuf {
resolve_codex_home_dir_from(
std::env::var_os("CODEX_HOME"),
dirs::home_dir(),
)
resolve_codex_home_dir_from(std::env::var_os("CODEX_HOME"), dirs::home_dir())
}
fn resolve_codex_home_dir_from(
@@ -1257,10 +1254,7 @@ mod tests {
#[test]
fn codex_home_defaults_to_home_dot_codex() {
let resolved = resolve_codex_home_dir_from(
None,
Some(PathBuf::from("/Users/default")),
);
let resolved = resolve_codex_home_dir_from(None, Some(PathBuf::from("/Users/default")));
assert_eq!(resolved, PathBuf::from("/Users/default/.codex"));
}
}

View File

@@ -466,10 +466,7 @@ impl GeminiParser {
}
fn resolve_gemini_base_dir() -> PathBuf {
resolve_gemini_base_dir_from(
std::env::var_os("GEMINI_CLI_HOME"),
dirs::home_dir(),
)
resolve_gemini_base_dir_from(std::env::var_os("GEMINI_CLI_HOME"), dirs::home_dir())
}
fn resolve_gemini_base_dir_from(
@@ -610,8 +607,8 @@ fn group_into_turns(messages: Vec<UnifiedMessage>) -> Vec<MessageTurn> {
#[cfg(test)]
mod tests {
use super::GeminiParser;
use super::resolve_gemini_base_dir_from;
use super::GeminiParser;
use crate::parsers::AgentParser;
use std::env;
use std::fs;
@@ -706,10 +703,7 @@ mod tests {
#[test]
fn gemini_defaults_to_home_dot_gemini() {
let resolved = resolve_gemini_base_dir_from(
None,
Some(PathBuf::from("/Users/default")),
);
let resolved = resolve_gemini_base_dir_from(None, Some(PathBuf::from("/Users/default")));
assert_eq!(resolved, PathBuf::from("/Users/default/.gemini"));
}
}

View File

@@ -251,8 +251,8 @@ mod tests {
use chrono::Utc;
use super::{
infer_context_window_max_tokens, latest_turn_total_usage_tokens, merge_context_window_stats,
path_eq_for_matching,
infer_context_window_max_tokens, latest_turn_total_usage_tokens,
merge_context_window_stats, path_eq_for_matching,
};
use crate::models::{MessageTurn, SessionStats, TurnRole, TurnUsage};

View File

@@ -634,13 +634,7 @@ mod tests {
#[test]
fn xdg_data_home_falls_back_to_home_local_share() {
let resolved = resolve_xdg_data_home(
None,
Some(PathBuf::from("/Users/default")),
);
assert_eq!(
resolved,
Some(PathBuf::from("/Users/default/.local/share"))
);
let resolved = resolve_xdg_data_home(None, Some(PathBuf::from("/Users/default")));
assert_eq!(resolved, Some(PathBuf::from("/Users/default/.local/share")));
}
}