fix(prompt): improve i18n and error handling for auto-import
Address code review feedback for PR #214: - Replace hardcoded Chinese strings with English in auto-imported prompts - Prompt name: "Auto-imported Prompt" instead of "初始提示词" - Description: "Automatically imported on first launch" - Remove panic risk by replacing expect() with proper error propagation - Use AppError::localized for bilingual error messages - Extract get_base_dir_with_fallback() helper to eliminate code duplication - Update test assertions to match new English strings - Suppress false-positive dead_code warning on TempHome.dir field All 5 tests passing with zero compiler warnings.
This commit is contained in:
@@ -234,10 +234,7 @@ impl MultiAppConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 检查并自动导入单个应用的提示词文件
|
/// 检查并自动导入单个应用的提示词文件
|
||||||
fn auto_import_prompt_if_exists(
|
fn auto_import_prompt_if_exists(config: &mut Self, app: AppType) -> Result<(), AppError> {
|
||||||
config: &mut Self,
|
|
||||||
app: AppType,
|
|
||||||
) -> Result<(), AppError> {
|
|
||||||
let file_path = prompt_file_path(&app)?;
|
let file_path = prompt_file_path(&app)?;
|
||||||
|
|
||||||
// 检查文件是否存在
|
// 检查文件是否存在
|
||||||
@@ -273,11 +270,11 @@ impl MultiAppConfig {
|
|||||||
let prompt = crate::prompt::Prompt {
|
let prompt = crate::prompt::Prompt {
|
||||||
id: id.clone(),
|
id: id.clone(),
|
||||||
name: format!(
|
name: format!(
|
||||||
"初始提示词 {}",
|
"Auto-imported Prompt {}",
|
||||||
chrono::Local::now().format("%Y-%m-%d %H:%M")
|
chrono::Local::now().format("%Y-%m-%d %H:%M")
|
||||||
),
|
),
|
||||||
content,
|
content,
|
||||||
description: Some("首次启动时自动导入".to_string()),
|
description: Some("Automatically imported on first launch".to_string()),
|
||||||
enabled: true, // 自动启用
|
enabled: true, // 自动启用
|
||||||
created_at: Some(timestamp),
|
created_at: Some(timestamp),
|
||||||
updated_at: Some(timestamp),
|
updated_at: Some(timestamp),
|
||||||
@@ -306,6 +303,7 @@ mod tests {
|
|||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
|
||||||
struct TempHome {
|
struct TempHome {
|
||||||
|
#[allow(dead_code)] // 字段通过 Drop trait 管理临时目录生命周期
|
||||||
dir: TempDir,
|
dir: TempDir,
|
||||||
original_home: Option<String>,
|
original_home: Option<String>,
|
||||||
original_userprofile: Option<String>,
|
original_userprofile: Option<String>,
|
||||||
@@ -444,7 +442,10 @@ mod tests {
|
|||||||
.expect("gemini prompt exists");
|
.expect("gemini prompt exists");
|
||||||
assert!(prompt.enabled, "gemini prompt should be enabled");
|
assert!(prompt.enabled, "gemini prompt should be enabled");
|
||||||
assert_eq!(prompt.content, "# Gemini Prompt\n\nTest content");
|
assert_eq!(prompt.content, "# Gemini Prompt\n\nTest content");
|
||||||
assert_eq!(prompt.description, Some("首次启动时自动导入".to_string()));
|
assert_eq!(
|
||||||
|
prompt.description,
|
||||||
|
Some("Automatically imported on first launch".to_string())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -463,8 +464,35 @@ mod tests {
|
|||||||
assert_eq!(config.prompts.gemini.prompts.len(), 1);
|
assert_eq!(config.prompts.gemini.prompts.len(), 1);
|
||||||
|
|
||||||
// 验证所有提示词都被启用
|
// 验证所有提示词都被启用
|
||||||
assert!(config.prompts.claude.prompts.values().next().unwrap().enabled);
|
assert!(
|
||||||
assert!(config.prompts.codex.prompts.values().next().unwrap().enabled);
|
config
|
||||||
assert!(config.prompts.gemini.prompts.values().next().unwrap().enabled);
|
.prompts
|
||||||
|
.claude
|
||||||
|
.prompts
|
||||||
|
.values()
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.enabled
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
config
|
||||||
|
.prompts
|
||||||
|
.codex
|
||||||
|
.prompts
|
||||||
|
.values()
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.enabled
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
config
|
||||||
|
.prompts
|
||||||
|
.gemini
|
||||||
|
.prompts
|
||||||
|
.values()
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.enabled
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,9 +58,7 @@ pub async fn import_prompt_from_file(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn get_current_prompt_file_content(
|
pub async fn get_current_prompt_file_content(app: String) -> Result<Option<String>, String> {
|
||||||
app: String,
|
|
||||||
) -> Result<Option<String>, String> {
|
|
||||||
let app_type = AppType::from_str(&app).map_err(|e| e.to_string())?;
|
let app_type = AppType::from_str(&app).map_err(|e| e.to_string())?;
|
||||||
PromptService::get_current_file_content(app_type).map_err(|e| e.to_string())
|
PromptService::get_current_file_content(app_type).map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,23 +8,9 @@ use crate::gemini_config::get_gemini_dir;
|
|||||||
|
|
||||||
/// 返回指定应用所使用的提示词文件路径。
|
/// 返回指定应用所使用的提示词文件路径。
|
||||||
pub fn prompt_file_path(app: &AppType) -> Result<PathBuf, AppError> {
|
pub fn prompt_file_path(app: &AppType) -> Result<PathBuf, AppError> {
|
||||||
let base_dir = match app {
|
let base_dir: PathBuf = match app {
|
||||||
AppType::Claude => get_claude_settings_path()
|
AppType::Claude => get_base_dir_with_fallback(get_claude_settings_path(), ".claude")?,
|
||||||
.parent()
|
AppType::Codex => get_base_dir_with_fallback(get_codex_auth_path(), ".codex")?,
|
||||||
.map(|p| p.to_path_buf())
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
dirs::home_dir()
|
|
||||||
.expect("无法获取用户目录")
|
|
||||||
.join(".claude")
|
|
||||||
}),
|
|
||||||
AppType::Codex => get_codex_auth_path()
|
|
||||||
.parent()
|
|
||||||
.map(|p| p.to_path_buf())
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
dirs::home_dir()
|
|
||||||
.expect("无法获取用户目录")
|
|
||||||
.join(".codex")
|
|
||||||
}),
|
|
||||||
AppType::Gemini => get_gemini_dir(),
|
AppType::Gemini => get_gemini_dir(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -36,3 +22,23 @@ pub fn prompt_file_path(app: &AppType) -> Result<PathBuf, AppError> {
|
|||||||
|
|
||||||
Ok(base_dir.join(filename))
|
Ok(base_dir.join(filename))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_base_dir_with_fallback(
|
||||||
|
primary_path: PathBuf,
|
||||||
|
fallback_dir: &str,
|
||||||
|
) -> Result<PathBuf, AppError> {
|
||||||
|
primary_path
|
||||||
|
.parent()
|
||||||
|
.map(|p| p.to_path_buf())
|
||||||
|
.or_else(|| dirs::home_dir().map(|h| h.join(fallback_dir)))
|
||||||
|
.ok_or_else(|| {
|
||||||
|
AppError::localized(
|
||||||
|
"home_dir_not_found",
|
||||||
|
format!("无法确定 {} 配置目录:用户主目录不存在", fallback_dir),
|
||||||
|
format!(
|
||||||
|
"Cannot determine {} config directory: user home not found",
|
||||||
|
fallback_dir
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -61,9 +61,7 @@ impl PromptService {
|
|||||||
|
|
||||||
if let Some(prompt) = prompts.get(id) {
|
if let Some(prompt) = prompts.get(id) {
|
||||||
if prompt.enabled {
|
if prompt.enabled {
|
||||||
return Err(AppError::InvalidInput(
|
return Err(AppError::InvalidInput("无法删除已启用的提示词".to_string()));
|
||||||
"无法删除已启用的提示词".to_string(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,7 +90,8 @@ impl PromptService {
|
|||||||
if let Ok(content) = std::fs::read_to_string(&target_path) {
|
if let Ok(content) = std::fs::read_to_string(&target_path) {
|
||||||
if !content.trim().is_empty() {
|
if !content.trim().is_empty() {
|
||||||
// 检查是否已存在相同内容的提示词,避免重复备份
|
// 检查是否已存在相同内容的提示词,避免重复备份
|
||||||
let content_exists = prompts.values().any(|p| p.content.trim() == content.trim());
|
let content_exists =
|
||||||
|
prompts.values().any(|p| p.content.trim() == content.trim());
|
||||||
|
|
||||||
if !content_exists {
|
if !content_exists {
|
||||||
let timestamp = std::time::SystemTime::now()
|
let timestamp = std::time::SystemTime::now()
|
||||||
@@ -102,7 +101,10 @@ impl PromptService {
|
|||||||
let backup_id = format!("backup-{timestamp}");
|
let backup_id = format!("backup-{timestamp}");
|
||||||
let backup_prompt = Prompt {
|
let backup_prompt = Prompt {
|
||||||
id: backup_id.clone(),
|
id: backup_id.clone(),
|
||||||
name: format!("原始提示词 {}", chrono::Local::now().format("%Y-%m-%d %H:%M")),
|
name: format!(
|
||||||
|
"原始提示词 {}",
|
||||||
|
chrono::Local::now().format("%Y-%m-%d %H:%M")
|
||||||
|
),
|
||||||
content,
|
content,
|
||||||
description: Some("自动备份的原始提示词".to_string()),
|
description: Some("自动备份的原始提示词".to_string()),
|
||||||
enabled: false,
|
enabled: false,
|
||||||
@@ -148,7 +150,8 @@ impl PromptService {
|
|||||||
return Err(AppError::Message("提示词文件不存在".to_string()));
|
return Err(AppError::Message("提示词文件不存在".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let content = std::fs::read_to_string(&file_path).map_err(|e| AppError::io(&file_path, e))?;
|
let content =
|
||||||
|
std::fs::read_to_string(&file_path).map_err(|e| AppError::io(&file_path, e))?;
|
||||||
let timestamp = std::time::SystemTime::now()
|
let timestamp = std::time::SystemTime::now()
|
||||||
.duration_since(std::time::UNIX_EPOCH)
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@@ -177,7 +180,8 @@ impl PromptService {
|
|||||||
if !file_path.exists() {
|
if !file_path.exists() {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
let content = std::fs::read_to_string(&file_path).map_err(|e| AppError::io(&file_path, e))?;
|
let content =
|
||||||
|
std::fs::read_to_string(&file_path).map_err(|e| AppError::io(&file_path, e))?;
|
||||||
Ok(Some(content))
|
Ok(Some(content))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user