diff --git a/package.json b/package.json index 985d71541..9d7e0f9ea 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "proxycast", "private": true, - "version": "0.55.0", + "version": "0.56.0", "type": "module", "repository": { "type": "git", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 0cccfa45f..89bfcc41c 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -6553,7 +6553,7 @@ dependencies = [ [[package]] name = "proxycast" -version = "0.55.0" +version = "0.56.0" dependencies = [ "anyhow", "arboard", @@ -6635,7 +6635,7 @@ dependencies = [ [[package]] name = "proxycast-core" -version = "0.55.0" +version = "0.56.0" dependencies = [ "chrono", "dirs 5.0.1", @@ -6651,7 +6651,7 @@ dependencies = [ [[package]] name = "proxycast-infra" -version = "0.55.0" +version = "0.56.0" dependencies = [ "chrono", "dashmap 5.5.3", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 3d8d7af29..5ff34d5ed 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -3,7 +3,7 @@ members = ["crates/*"] resolver = "2" [workspace.package] -version = "0.55.0" +version = "0.56.0" edition = "2021" authors = ["you"] repository = "https://github.com/aiclientproxy/proxycast" @@ -164,7 +164,7 @@ version = "2.4" [package] name = "proxycast" -version = "0.55.0" +version = "0.56.0" description = "AI API Proxy Desktop App" authors = ["you"] edition = "2021" diff --git a/src-tauri/src/README.md b/src-tauri/src/README.md index 3fd7ee5ac..56456c912 100644 --- a/src-tauri/src/README.md +++ b/src-tauri/src/README.md @@ -15,6 +15,7 @@ Tauri 后端核心代码,处理系统级功能和 API 服务。 - `converter/` - 协议转换(OpenAI ↔ CW/Claude/Antigravity) - `credential/` - 凭证池管理(负载均衡、健康检查) - `database/` - 数据库层(SQLite + DAO) +- `errors/` - 错误类型定义(项目、人设、素材、模板、迁移错误) - `flow_monitor/` - LLM 流量监控(拦截、存储、查询) - `injection/` - 请求注入(系统提示词等) - `middleware/` - HTTP 中间件 diff --git a/src-tauri/src/agent/aster_state.rs b/src-tauri/src/agent/aster_state.rs index c70ce592b..43e81297d 100644 --- a/src-tauri/src/agent/aster_state.rs +++ b/src-tauri/src/agent/aster_state.rs @@ -28,6 +28,7 @@ use crate::agent::credential_bridge::{ }; use crate::database::DbConnection; use crate::services::aster_session_store::ProxyCastSessionStore; +use crate::services::project_context_builder::ProjectContextBuilder; /// Provider 配置信息 #[derive(Debug, Clone)] @@ -396,6 +397,57 @@ impl AsterAgentState { tokens.remove(session_id); } + // ------------------------------------------------------------------------ + // 项目上下文支持 + // ------------------------------------------------------------------------ + + /// 构建带项目上下文的 System Prompt + /// + /// 加载项目的人设、素材、模板配置,构建完整的 AI 提示词。 + /// + /// # 参数 + /// - `db`: 数据库连接 + /// - `project_id`: 项目 ID + /// + /// # 返回 + /// - 成功返回构建好的 System Prompt + /// - 失败返回错误信息 + pub fn build_project_system_prompt( + db: &DbConnection, + project_id: &str, + ) -> Result { + let conn = db + .lock() + .map_err(|e| format!("获取数据库连接失败: {}", e))?; + let context = ProjectContextBuilder::build_context(&conn, project_id) + .map_err(|e| format!("构建项目上下文失败: {}", e))?; + Ok(ProjectContextBuilder::build_system_prompt(&context)) + } + + /// 创建带项目上下文的会话配置 + /// + /// 自动加载项目配置并构建 SessionConfig。 + /// + /// # 参数 + /// - `db`: 数据库连接 + /// - `session_id`: 会话 ID + /// - `project_id`: 项目 ID(可选,如果为 None 则不注入项目上下文) + /// + /// # 返回 + /// - 构建好的 SessionConfig + pub fn create_session_config_with_project( + db: &DbConnection, + session_id: &str, + project_id: Option<&str>, + ) -> SessionConfig { + let system_prompt = + project_id.and_then(|pid| Self::build_project_system_prompt(db, pid).ok()); + + SessionConfigBuilder::new(session_id) + .system_prompt(system_prompt.unwrap_or_default()) + .build() + } + /// 检查 Agent 是否已初始化 pub async fn is_initialized(&self) -> bool { self.agent.read().await.is_some() diff --git a/src-tauri/src/app/runner.rs b/src-tauri/src/app/runner.rs index d7692ff8b..3d27f4cbd 100644 --- a/src-tauri/src/app/runner.rs +++ b/src-tauri/src/app/runner.rs @@ -1247,6 +1247,35 @@ pub fn run() { commands::workspace_cmd::workspace_set_default, commands::workspace_cmd::workspace_get_default, commands::workspace_cmd::workspace_get_by_path, + commands::workspace_cmd::get_or_create_default_project, + commands::workspace_cmd::get_project_context, + commands::workspace_cmd::build_project_system_prompt, + // Persona commands + commands::persona_cmd::create_persona, + commands::persona_cmd::list_personas, + commands::persona_cmd::get_persona, + commands::persona_cmd::update_persona, + commands::persona_cmd::delete_persona, + commands::persona_cmd::set_default_persona, + commands::persona_cmd::list_persona_templates, + commands::persona_cmd::get_default_persona, + // Material commands + commands::material_cmd::upload_material, + commands::material_cmd::list_materials, + commands::material_cmd::get_material, + commands::material_cmd::update_material, + commands::material_cmd::delete_material, + commands::material_cmd::get_material_content, + commands::material_cmd::get_material_count, + commands::material_cmd::get_materials_content, + // Template commands + commands::template_cmd::create_template, + commands::template_cmd::list_templates, + commands::template_cmd::get_template, + commands::template_cmd::update_template, + commands::template_cmd::delete_template, + commands::template_cmd::set_default_template, + commands::template_cmd::get_default_template, // Content commands commands::content_cmd::content_create, commands::content_cmd::content_get, diff --git a/src-tauri/src/commands/aster_agent_cmd.rs b/src-tauri/src/commands/aster_agent_cmd.rs index 094d10ead..fa197683e 100644 --- a/src-tauri/src/commands/aster_agent_cmd.rs +++ b/src-tauri/src/commands/aster_agent_cmd.rs @@ -167,6 +167,9 @@ pub struct AsterChatRequest { /// Provider 配置(可选,如果未配置则使用当前配置) #[serde(default)] pub provider_config: Option, + /// 项目 ID(可选,用于注入项目上下文到 System Prompt) + #[serde(default)] + pub project_id: Option, } /// 图片输入 @@ -214,30 +217,59 @@ pub async fn aster_agent_chat_stream( // 同时 get_session 也会自动创建不存在的 session let session_id = &request.session_id; - // 从 ProxyCast 数据库读取 session 的 system_prompt(如果存在) + // 构建 system_prompt:优先使用项目上下文,其次使用 session 的 system_prompt let system_prompt = { let db_conn = db.lock().map_err(|e| format!("获取数据库连接失败: {e}"))?; - match AgentDao::get_session(&db_conn, session_id) { - Ok(Some(session)) => { - tracing::debug!( - "[AsterAgent] 找到 session,system_prompt: {:?}", - session.system_prompt.as_ref().map(|s| s.len()) - ); - session.system_prompt + + // 1. 如果提供了 project_id,构建项目上下文 + let project_prompt = if let Some(ref project_id) = request.project_id { + match AsterAgentState::build_project_system_prompt(&db, project_id) { + Ok(prompt) => { + tracing::info!( + "[AsterAgent] 已加载项目上下文: project_id={}, prompt_len={}", + project_id, + prompt.len() + ); + Some(prompt) + } + Err(e) => { + tracing::warn!( + "[AsterAgent] 加载项目上下文失败: {}, 继续使用 session prompt", + e + ); + None + } } - Ok(None) => { - tracing::debug!( - "[AsterAgent] ProxyCast 数据库中未找到 session: {}", - session_id - ); - None - } - Err(e) => { - tracing::warn!( - "[AsterAgent] 读取 session 失败: {}, 继续使用空 system_prompt", - e - ); - None + } else { + None + }; + + // 2. 如果没有项目上下文,尝试从 session 读取 + if project_prompt.is_some() { + project_prompt + } else { + match AgentDao::get_session(&db_conn, session_id) { + Ok(Some(session)) => { + tracing::debug!( + "[AsterAgent] 找到 session,system_prompt: {:?}", + session.system_prompt.as_ref().map(|s| s.len()) + ); + session.system_prompt + } + Ok(None) => { + tracing::debug!( + "[AsterAgent] ProxyCast 数据库中未找到 session: {}", + session_id + ); + None + } + Err(e) => { + tracing::warn!( + "[AsterAgent] 读取 session 失败: {}, 继续使用空 system_prompt", + e + ); + None + } } } }; diff --git a/src-tauri/src/commands/material_cmd.rs b/src-tauri/src/commands/material_cmd.rs new file mode 100644 index 000000000..f0bf135d6 --- /dev/null +++ b/src-tauri/src/commands/material_cmd.rs @@ -0,0 +1,276 @@ +//! 素材相关的 Tauri 命令 +//! +//! 提供素材(Material)管理的前端 API,包括: +//! - 上传、获取、列表、更新、删除素材 +//! - 获取素材内容(用于 AI 引用) +//! +//! ## 相关需求 +//! - Requirements 7.1: 素材列表显示 +//! - Requirements 7.2: 上传素材按钮 +//! - Requirements 7.3: 素材创建 +//! - Requirements 7.4: 素材搜索和筛选 +//! - Requirements 7.5: 素材预览 +//! - Requirements 7.6: 素材删除 + +use tauri::State; + +use crate::database::DbConnection; +use crate::models::project_model::{ + Material, MaterialFilter, MaterialUpdate, UploadMaterialRequest, +}; +use crate::services::material_service::MaterialService; + +// ============================================================================ +// Tauri 命令 +// ============================================================================ + +/// 上传素材 +/// +/// 在指定项目中上传新的素材。支持文档、图片、文本、数据文件等类型。 +/// +/// # 参数 +/// - `db`: 数据库连接状态 +/// - `req`: 上传素材请求,包含项目 ID、名称、类型、文件路径等信息 +/// +/// # 返回 +/// - 成功返回创建的素材 +/// - 失败返回错误信息 +/// +/// # 示例(前端调用) +/// ```typescript +/// const material = await invoke('upload_material', { +/// req: { +/// project_id: 'project-1', +/// name: '参考文档.pdf', +/// type: 'document', +/// file_path: '/tmp/upload.pdf', +/// tags: ['参考', '重要'], +/// description: '项目参考文档', +/// } +/// }); +/// ``` +#[tauri::command] +pub async fn upload_material( + db: State<'_, DbConnection>, + req: UploadMaterialRequest, +) -> Result { + let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?; + MaterialService::upload_material(&conn, req).map_err(|e| e.to_string()) +} + +/// 获取项目的素材列表 +/// +/// 获取指定项目下的所有素材,支持按类型、标签和关键词筛选。 +/// +/// # 参数 +/// - `db`: 数据库连接状态 +/// - `project_id`: 项目 ID +/// - `filter`: 可选的筛选条件 +/// +/// # 返回 +/// - 成功返回素材列表 +/// - 失败返回错误信息 +/// +/// # 示例(前端调用) +/// ```typescript +/// // 获取所有素材 +/// const materials = await invoke('list_materials', { +/// projectId: 'project-1' +/// }); +/// +/// // 按类型筛选 +/// const documents = await invoke('list_materials', { +/// projectId: 'project-1', +/// filter: { type: 'document' } +/// }); +/// +/// // 按标签筛选 +/// const important = await invoke('list_materials', { +/// projectId: 'project-1', +/// filter: { tags: ['重要'] } +/// }); +/// ``` +#[tauri::command] +pub async fn list_materials( + db: State<'_, DbConnection>, + project_id: String, + filter: Option, +) -> Result, String> { + let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?; + MaterialService::list_materials(&conn, &project_id, filter).map_err(|e| e.to_string()) +} + +/// 获取单个素材 +/// +/// 根据 ID 获取素材详情。 +/// +/// # 参数 +/// - `db`: 数据库连接状态 +/// - `id`: 素材 ID +/// +/// # 返回 +/// - 成功返回 Option,不存在时返回 None +/// - 失败返回错误信息 +/// +/// # 示例(前端调用) +/// ```typescript +/// const material = await invoke('get_material', { +/// id: 'material-1' +/// }); +/// ``` +#[tauri::command] +pub async fn get_material( + db: State<'_, DbConnection>, + id: String, +) -> Result, String> { + let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?; + MaterialService::get_material(&conn, &id).map_err(|e| e.to_string()) +} + +/// 更新素材元数据 +/// +/// 更新指定素材的元数据信息(名称、标签、描述)。 +/// 注意:不能更新文件内容,如需更新文件请删除后重新上传。 +/// +/// # 参数 +/// - `db`: 数据库连接状态 +/// - `id`: 素材 ID +/// - `update`: 更新内容,只包含需要更新的字段 +/// +/// # 返回 +/// - 成功返回更新后的素材 +/// - 失败返回错误信息 +/// +/// # 示例(前端调用) +/// ```typescript +/// const material = await invoke('update_material', { +/// id: 'material-1', +/// update: { +/// name: '新名称', +/// tags: ['新标签'], +/// description: '新描述', +/// } +/// }); +/// ``` +#[tauri::command] +pub async fn update_material( + db: State<'_, DbConnection>, + id: String, + update: MaterialUpdate, +) -> Result { + let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?; + MaterialService::update_material(&conn, &id, update).map_err(|e| e.to_string()) +} + +/// 删除素材 +/// +/// 删除指定的素材,同时删除数据库记录和文件系统中的文件。 +/// 此操作不可逆,请谨慎使用。 +/// +/// # 参数 +/// - `db`: 数据库连接状态 +/// - `id`: 素材 ID +/// +/// # 返回 +/// - 成功返回 () +/// - 失败返回错误信息 +/// +/// # 示例(前端调用) +/// ```typescript +/// await invoke('delete_material', { +/// id: 'material-1' +/// }); +/// ``` +#[tauri::command] +pub async fn delete_material(db: State<'_, DbConnection>, id: String) -> Result<(), String> { + let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?; + MaterialService::delete_material(&conn, &id).map_err(|e| e.to_string()) +} + +/// 获取素材内容 +/// +/// 获取素材的文本内容,用于 AI 引用。 +/// 根据素材类型返回不同的内容: +/// - text 类型:返回 content 字段或读取文件内容 +/// - document 类型:对于文本文件返回内容,其他返回描述信息 +/// - image/data/link 类型:返回描述信息 +/// +/// # 参数 +/// - `db`: 数据库连接状态 +/// - `id`: 素材 ID +/// +/// # 返回 +/// - 成功返回素材内容字符串 +/// - 失败返回错误信息 +/// +/// # 示例(前端调用) +/// ```typescript +/// const content = await invoke('get_material_content', { +/// id: 'material-1' +/// }); +/// ``` +#[tauri::command] +pub async fn get_material_content( + db: State<'_, DbConnection>, + id: String, +) -> Result { + let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?; + MaterialService::get_material_content(&conn, &id).map_err(|e| e.to_string()) +} + +/// 获取项目的素材数量 +/// +/// 获取指定项目下的素材总数。 +/// +/// # 参数 +/// - `db`: 数据库连接状态 +/// - `project_id`: 项目 ID +/// +/// # 返回 +/// - 成功返回素材数量 +/// - 失败返回错误信息 +/// +/// # 示例(前端调用) +/// ```typescript +/// const count = await invoke('get_material_count', { +/// projectId: 'project-1' +/// }); +/// ``` +#[tauri::command] +pub async fn get_material_count( + db: State<'_, DbConnection>, + project_id: String, +) -> Result { + let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?; + crate::database::dao::material_dao::MaterialDao::count(&conn, &project_id) + .map_err(|e| e.to_string()) +} + +/// 批量获取素材内容 +/// +/// 获取项目下所有素材的内容,用于构建项目上下文。 +/// 返回素材名称和内容的列表。 +/// +/// # 参数 +/// - `db`: 数据库连接状态 +/// - `project_id`: 项目 ID +/// +/// # 返回 +/// - 成功返回素材内容列表 [(name, content), ...] +/// - 失败返回错误信息 +/// +/// # 示例(前端调用) +/// ```typescript +/// const contents = await invoke('get_materials_content', { +/// projectId: 'project-1' +/// }); +/// // contents: [['文档1', '内容1'], ['文档2', '内容2']] +/// ``` +#[tauri::command] +pub async fn get_materials_content( + db: State<'_, DbConnection>, + project_id: String, +) -> Result, String> { + let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?; + MaterialService::get_materials_content(&conn, &project_id).map_err(|e| e.to_string()) +} diff --git a/src-tauri/src/commands/mod.rs b/src-tauri/src/commands/mod.rs index 447bf26d6..2e386e5ea 100644 --- a/src-tauri/src/commands/mod.rs +++ b/src-tauri/src/commands/mod.rs @@ -14,6 +14,7 @@ pub mod general_chat_cmd; pub mod injection_cmd; pub mod kiro_local; pub mod machine_id_cmd; +pub mod material_cmd; pub mod mcp_cmd; pub mod memory_cmd; pub mod model_cmd; @@ -23,6 +24,7 @@ pub mod music_cmd; pub mod network_cmd; pub mod oauth_cmd; pub mod orchestrator_cmd; +pub mod persona_cmd; pub mod plugin_cmd; pub mod plugin_install_cmd; pub mod plugin_rpc_cmd; @@ -35,6 +37,7 @@ pub mod session_files_cmd; pub mod skill_cmd; pub mod switch_cmd; pub mod telemetry_cmd; +pub mod template_cmd; pub mod terminal_cmd; pub mod tool_hooks; pub mod tray_cmd; diff --git a/src-tauri/src/commands/persona_cmd.rs b/src-tauri/src/commands/persona_cmd.rs new file mode 100644 index 000000000..e2860950b --- /dev/null +++ b/src-tauri/src/commands/persona_cmd.rs @@ -0,0 +1,242 @@ +//! 人设相关的 Tauri 命令 +//! +//! 提供人设(Persona)管理的前端 API,包括: +//! - 创建、获取、列表、更新、删除人设 +//! - 设置项目默认人设 +//! - 获取人设模板列表 +//! +//! ## 相关需求 +//! - Requirements 6.1: 人设列表显示 +//! - Requirements 6.2: 创建人设按钮 +//! - Requirements 6.3: 人设创建表单 +//! - Requirements 6.4: 设置默认人设 +//! - Requirements 6.5: 人设模板 +//! - Requirements 6.6: 人设删除确认 + +use tauri::State; + +use crate::database::DbConnection; +use crate::models::project_model::{CreatePersonaRequest, Persona, PersonaTemplate, PersonaUpdate}; +use crate::services::persona_service::PersonaService; + +// ============================================================================ +// Tauri 命令 +// ============================================================================ + +/// 创建人设 +/// +/// 在指定项目中创建新的人设配置。 +/// +/// # 参数 +/// - `db`: 数据库连接状态 +/// - `req`: 创建人设请求,包含项目 ID、名称、风格等信息 +/// +/// # 返回 +/// - 成功返回创建的人设 +/// - 失败返回错误信息 +/// +/// # 示例(前端调用) +/// ```typescript +/// const persona = await invoke('create_persona', { +/// req: { +/// project_id: 'project-1', +/// name: '专业写手', +/// style: '专业严谨', +/// tone: '正式', +/// } +/// }); +/// ``` +#[tauri::command] +pub async fn create_persona( + db: State<'_, DbConnection>, + req: CreatePersonaRequest, +) -> Result { + let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?; + PersonaService::create_persona(&conn, req).map_err(|e| e.to_string()) +} + +/// 获取项目的人设列表 +/// +/// 获取指定项目下的所有人设配置。 +/// +/// # 参数 +/// - `db`: 数据库连接状态 +/// - `project_id`: 项目 ID +/// +/// # 返回 +/// - 成功返回人设列表 +/// - 失败返回错误信息 +/// +/// # 示例(前端调用) +/// ```typescript +/// const personas = await invoke('list_personas', { +/// projectId: 'project-1' +/// }); +/// ``` +#[tauri::command] +pub async fn list_personas( + db: State<'_, DbConnection>, + project_id: String, +) -> Result, String> { + let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?; + PersonaService::list_personas(&conn, &project_id).map_err(|e| e.to_string()) +} + +/// 获取单个人设 +/// +/// 根据 ID 获取人设详情。 +/// +/// # 参数 +/// - `db`: 数据库连接状态 +/// - `id`: 人设 ID +/// +/// # 返回 +/// - 成功返回 Option,不存在时返回 None +/// - 失败返回错误信息 +/// +/// # 示例(前端调用) +/// ```typescript +/// const persona = await invoke('get_persona', { +/// id: 'persona-1' +/// }); +/// ``` +#[tauri::command] +pub async fn get_persona( + db: State<'_, DbConnection>, + id: String, +) -> Result, String> { + let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?; + PersonaService::get_persona(&conn, &id).map_err(|e| e.to_string()) +} + +/// 更新人设 +/// +/// 更新指定人设的配置信息。 +/// +/// # 参数 +/// - `db`: 数据库连接状态 +/// - `id`: 人设 ID +/// - `update`: 更新内容,只包含需要更新的字段 +/// +/// # 返回 +/// - 成功返回更新后的人设 +/// - 失败返回错误信息 +/// +/// # 示例(前端调用) +/// ```typescript +/// const persona = await invoke('update_persona', { +/// id: 'persona-1', +/// update: { +/// name: '新名称', +/// style: '新风格', +/// } +/// }); +/// ``` +#[tauri::command] +pub async fn update_persona( + db: State<'_, DbConnection>, + id: String, + update: PersonaUpdate, +) -> Result { + let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?; + PersonaService::update_persona(&conn, &id, update).map_err(|e| e.to_string()) +} + +/// 删除人设 +/// +/// 删除指定的人设配置。 +/// +/// # 参数 +/// - `db`: 数据库连接状态 +/// - `id`: 人设 ID +/// +/// # 返回 +/// - 成功返回 () +/// - 失败返回错误信息 +/// +/// # 示例(前端调用) +/// ```typescript +/// await invoke('delete_persona', { +/// id: 'persona-1' +/// }); +/// ``` +#[tauri::command] +pub async fn delete_persona(db: State<'_, DbConnection>, id: String) -> Result<(), String> { + let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?; + PersonaService::delete_persona(&conn, &id).map_err(|e| e.to_string()) +} + +/// 设置项目默认人设 +/// +/// 将指定人设设为项目的默认人设。 +/// 同一项目只能有一个默认人设,设置新默认会自动取消原有默认。 +/// +/// # 参数 +/// - `db`: 数据库连接状态 +/// - `project_id`: 项目 ID +/// - `persona_id`: 要设为默认的人设 ID +/// +/// # 返回 +/// - 成功返回 () +/// - 失败返回错误信息 +/// +/// # 示例(前端调用) +/// ```typescript +/// await invoke('set_default_persona', { +/// projectId: 'project-1', +/// personaId: 'persona-1' +/// }); +/// ``` +#[tauri::command] +pub async fn set_default_persona( + db: State<'_, DbConnection>, + project_id: String, + persona_id: String, +) -> Result<(), String> { + let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?; + PersonaService::set_default_persona(&conn, &project_id, &persona_id).map_err(|e| e.to_string()) +} + +/// 获取人设模板列表 +/// +/// 获取预定义的人设模板,用于快速创建人设。 +/// 模板包含常见的写作风格配置,如专业写手、生活博主等。 +/// +/// # 返回 +/// - 人设模板列表 +/// +/// # 示例(前端调用) +/// ```typescript +/// const templates = await invoke('list_persona_templates'); +/// ``` +#[tauri::command] +pub async fn list_persona_templates() -> Result, String> { + Ok(PersonaService::list_persona_templates()) +} + +/// 获取项目的默认人设 +/// +/// 获取指定项目的默认人设配置。 +/// +/// # 参数 +/// - `db`: 数据库连接状态 +/// - `project_id`: 项目 ID +/// +/// # 返回 +/// - 成功返回 Option,没有默认人设时返回 None +/// - 失败返回错误信息 +/// +/// # 示例(前端调用) +/// ```typescript +/// const defaultPersona = await invoke('get_default_persona', { +/// projectId: 'project-1' +/// }); +/// ``` +#[tauri::command] +pub async fn get_default_persona( + db: State<'_, DbConnection>, + project_id: String, +) -> Result, String> { + let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?; + PersonaService::get_default_persona(&conn, &project_id).map_err(|e| e.to_string()) +} diff --git a/src-tauri/src/commands/template_cmd.rs b/src-tauri/src/commands/template_cmd.rs new file mode 100644 index 000000000..781cc845e --- /dev/null +++ b/src-tauri/src/commands/template_cmd.rs @@ -0,0 +1,226 @@ +//! 排版模板相关的 Tauri 命令 +//! +//! 提供排版模板(Template)管理的前端 API,包括: +//! - 创建、获取、列表、更新、删除模板 +//! - 设置项目默认模板 +//! +//! ## 相关需求 +//! - Requirements 8.1: 模板列表显示 +//! - Requirements 8.2: 创建模板按钮 +//! - Requirements 8.3: 模板创建表单 +//! - Requirements 8.4: 设置默认模板 +//! - Requirements 8.5: 模板预览功能 + +use tauri::State; + +use crate::database::DbConnection; +use crate::models::project_model::{CreateTemplateRequest, Template, TemplateUpdate}; +use crate::services::template_service::TemplateService; + +// ============================================================================ +// Tauri 命令 +// ============================================================================ + +/// 创建排版模板 +/// +/// 在指定项目中创建新的排版模板。 +/// +/// # 参数 +/// - `db`: 数据库连接状态 +/// - `req`: 创建模板请求,包含项目 ID、名称、平台、样式规则等信息 +/// +/// # 返回 +/// - 成功返回创建的模板 +/// - 失败返回错误信息 +/// +/// # 示例(前端调用) +/// ```typescript +/// const template = await invoke('create_template', { +/// req: { +/// project_id: 'project-1', +/// name: '小红书模板', +/// platform: 'xiaohongshu', +/// title_style: '吸引眼球', +/// emoji_usage: 'heavy', +/// } +/// }); +/// ``` +#[tauri::command] +pub async fn create_template( + db: State<'_, DbConnection>, + req: CreateTemplateRequest, +) -> Result { + let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?; + TemplateService::create_template(&conn, req).map_err(|e| e.to_string()) +} + +/// 获取项目的模板列表 +/// +/// 获取指定项目下的所有排版模板。 +/// +/// # 参数 +/// - `db`: 数据库连接状态 +/// - `project_id`: 项目 ID +/// +/// # 返回 +/// - 成功返回模板列表 +/// - 失败返回错误信息 +/// +/// # 示例(前端调用) +/// ```typescript +/// const templates = await invoke('list_templates', { +/// projectId: 'project-1' +/// }); +/// ``` +#[tauri::command] +pub async fn list_templates( + db: State<'_, DbConnection>, + project_id: String, +) -> Result, String> { + let conn = db.lock().map_err(|e| format!("数据库锁定失败: {e}"))?; + TemplateService::list_templates(&conn, &project_id).map_err(|e| e.to_string()) +} + +/// 获取单个模板 +/// +/// 根据 ID 获取模板详情。 +/// +/// # 参数 +/// - `db`: 数据库连接状态 +/// - `id`: 模板 ID +/// +/// # 返回 +/// - 成功返回 Option