use indexmap::IndexMap; use crate::app_config::AppType; use crate::config::write_text_file; use crate::error::AppError; use crate::prompt::Prompt; use crate::prompt_files::prompt_file_path; use crate::store::AppState; /// 安全地获取当前 Unix 时间戳 fn get_unix_timestamp() -> Result { std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .map(|d| d.as_secs() as i64) .map_err(|e| AppError::Message(format!("Failed to get system time: {e}"))) } pub struct PromptService; impl PromptService { pub fn get_prompts( state: &AppState, app: AppType, ) -> Result, AppError> { state.db.get_prompts(app.as_str()) } pub fn upsert_prompt( state: &AppState, app: AppType, _id: &str, prompt: Prompt, ) -> Result<(), AppError> { // 检查是否为已启用的提示词 let is_enabled = prompt.enabled; state.db.save_prompt(app.as_str(), &prompt)?; // 如果是已启用的提示词,同步更新到对应的文件 if is_enabled { let target_path = prompt_file_path(&app)?; write_text_file(&target_path, &prompt.content)?; } Ok(()) } pub fn delete_prompt(state: &AppState, app: AppType, id: &str) -> Result<(), AppError> { let prompts = state.db.get_prompts(app.as_str())?; if let Some(prompt) = prompts.get(id) { if prompt.enabled { return Err(AppError::InvalidInput("无法删除已启用的提示词".to_string())); } } state.db.delete_prompt(app.as_str(), id)?; Ok(()) } pub fn enable_prompt(state: &AppState, app: AppType, id: &str) -> Result<(), AppError> { // 回填当前 live 文件内容到已启用的提示词,或创建备份 let target_path = prompt_file_path(&app)?; if target_path.exists() { if let Ok(live_content) = std::fs::read_to_string(&target_path) { if !live_content.trim().is_empty() { let mut prompts = state.db.get_prompts(app.as_str())?; // 尝试回填到当前已启用的提示词 if let Some((enabled_id, enabled_prompt)) = prompts .iter_mut() .find(|(_, p)| p.enabled) .map(|(id, p)| (id.clone(), p)) { let timestamp = get_unix_timestamp()?; enabled_prompt.content = live_content.clone(); enabled_prompt.updated_at = Some(timestamp); log::info!("回填 live 提示词内容到已启用项: {enabled_id}"); state.db.save_prompt(app.as_str(), enabled_prompt)?; } else { // 没有已启用的提示词,则创建一次备份(避免重复备份) let content_exists = prompts .values() .any(|p| p.content.trim() == live_content.trim()); if !content_exists { let timestamp = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_secs() as i64; let backup_id = format!("backup-{timestamp}"); let backup_prompt = Prompt { id: backup_id.clone(), name: format!( "原始提示词 {}", chrono::Local::now().format("%Y-%m-%d %H:%M") ), content: live_content, description: Some("自动备份的原始提示词".to_string()), enabled: false, created_at: Some(timestamp), updated_at: Some(timestamp), }; log::info!("回填 live 提示词内容,创建备份: {backup_id}"); state.db.save_prompt(app.as_str(), &backup_prompt)?; } } } } } // 启用目标提示词并写入文件 let mut prompts = state.db.get_prompts(app.as_str())?; for prompt in prompts.values_mut() { prompt.enabled = false; } if let Some(prompt) = prompts.get_mut(id) { prompt.enabled = true; write_text_file(&target_path, &prompt.content)?; // 原子写入 state.db.save_prompt(app.as_str(), prompt)?; } else { return Err(AppError::InvalidInput(format!("提示词 {id} 不存在"))); } // Save all prompts to disable others for (_, prompt) in prompts.iter() { state.db.save_prompt(app.as_str(), prompt)?; } Ok(()) } pub fn import_from_file(state: &AppState, app: AppType) -> Result { let file_path = prompt_file_path(&app)?; if !file_path.exists() { return Err(AppError::Message("提示词文件不存在".to_string())); } let content = std::fs::read_to_string(&file_path).map_err(|e| AppError::io(&file_path, e))?; let timestamp = get_unix_timestamp()?; let id = format!("imported-{timestamp}"); let prompt = Prompt { id: id.clone(), name: format!( "导入的提示词 {}", chrono::Local::now().format("%Y-%m-%d %H:%M") ), content, description: Some("从现有配置文件导入".to_string()), enabled: false, created_at: Some(timestamp), updated_at: Some(timestamp), }; Self::upsert_prompt(state, app, &id, prompt)?; Ok(id) } pub fn get_current_file_content(app: AppType) -> Result, AppError> { let file_path = prompt_file_path(&app)?; if !file_path.exists() { return Ok(None); } let content = std::fs::read_to_string(&file_path).map_err(|e| AppError::io(&file_path, e))?; Ok(Some(content)) } /// 首次启动时从现有提示词文件自动导入(如果存在) /// 返回导入的数量 pub fn import_from_file_on_first_launch( state: &AppState, app: AppType, ) -> Result { // 幂等性保护:该应用已有提示词则跳过 let existing = state.db.get_prompts(app.as_str())?; if !existing.is_empty() { return Ok(0); } let file_path = prompt_file_path(&app)?; // 检查文件是否存在 if !file_path.exists() { return Ok(0); } // 读取文件内容 let content = match std::fs::read_to_string(&file_path) { Ok(c) => c, Err(e) => { log::warn!("读取提示词文件失败: {file_path:?}, 错误: {e}"); return Ok(0); } }; // 检查内容是否为空 if content.trim().is_empty() { return Ok(0); } log::info!("发现提示词文件,自动导入: {file_path:?}"); // 创建提示词对象 let timestamp = get_unix_timestamp()?; let id = format!("auto-imported-{timestamp}"); let prompt = Prompt { id: id.clone(), name: format!( "Auto-imported Prompt {}", chrono::Local::now().format("%Y-%m-%d %H:%M") ), content, description: Some("Automatically imported on first launch".to_string()), enabled: true, // 首次导入时自动启用 created_at: Some(timestamp), updated_at: Some(timestamp), }; // 保存到数据库 state.db.save_prompt(app.as_str(), &prompt)?; log::info!("自动导入完成: {}", app.as_str()); Ok(1) } }