From b2b64ec15dceb58bd1a740c02dbb28aa314348ae Mon Sep 17 00:00:00 2001 From: iven Date: Sat, 25 Apr 2026 22:51:47 +0800 Subject: [PATCH] =?UTF-8?q?feat(ai):=20PromptService=20=E8=A1=A5=E5=85=A8?= =?UTF-8?q?=20list/update/activate/rollback=20=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/erp-ai/src/service/prompt.rs | 124 +++++++++++++++++++++++++++- 1 file changed, 123 insertions(+), 1 deletion(-) diff --git a/crates/erp-ai/src/service/prompt.rs b/crates/erp-ai/src/service/prompt.rs index 0ae018f..1999084 100644 --- a/crates/erp-ai/src/service/prompt.rs +++ b/crates/erp-ai/src/service/prompt.rs @@ -1,8 +1,12 @@ -use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set}; +use sea_orm::ActiveModelTrait; +use sea_orm::QuerySelect; +use sea_orm::Set; +use sea_orm::{ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder}; use uuid::Uuid; use crate::entity::ai_prompt; use crate::error::{AiError, AiResult}; +use erp_core::types::Pagination; pub struct PromptService { pub db: sea_orm::DatabaseConnection, @@ -64,4 +68,122 @@ impl PromptService { }; Ok(active.insert(&self.db).await?) } + + /// 分页列出 Prompt 模板 + pub async fn list_prompts( + &self, + tenant_id: Uuid, + category: Option, + pagination: &Pagination, + ) -> AiResult<(Vec, u64)> { + let mut query = ai_prompt::Entity::find() + .filter(ai_prompt::Column::TenantId.eq(tenant_id)) + .filter(ai_prompt::Column::DeletedAt.is_null()); + + if let Some(cat) = &category { + query = query.filter(ai_prompt::Column::Category.eq(cat.as_str())); + } + + let total = query.clone().count(&self.db).await?; + let items = query + .order_by_desc(ai_prompt::Column::UpdatedAt) + .offset(pagination.offset()) + .limit(pagination.limit()) + .all(&self.db) + .await?; + Ok((items, total)) + } + + /// 更新 Prompt(创建新版本) + pub async fn update_prompt( + &self, + id: Uuid, + tenant_id: Uuid, + user_id: Uuid, + system_prompt: Option, + user_prompt_template: Option, + model_config: Option, + description: Option, + ) -> AiResult { + let entity = ai_prompt::Entity::find_by_id(id) + .one(&self.db) + .await? + .ok_or_else(|| AiError::PromptNotFound(id.to_string()))?; + + if entity.tenant_id != tenant_id { + return Err(AiError::Validation("跨租户操作".into())); + } + + let new_id = Uuid::now_v7(); + let now = chrono::Utc::now(); + let active = ai_prompt::ActiveModel { + id: Set(new_id), + tenant_id: Set(tenant_id), + name: Set(entity.name.clone()), + description: Set(description.unwrap_or(entity.description.clone())), + system_prompt: Set(system_prompt.unwrap_or(entity.system_prompt.clone())), + user_prompt_template: Set(user_prompt_template.unwrap_or(entity.user_prompt_template.clone())), + variables_schema: Set(entity.variables_schema.clone()), + model_config: Set(model_config.unwrap_or(entity.model_config.clone())), + version: Set(entity.version + 1), + is_active: Set(entity.is_active), + category: Set(entity.category.clone()), + tags: Set(entity.tags.clone()), + created_at: Set(now), + updated_at: Set(now), + created_by: Set(Some(user_id)), + updated_by: Set(Some(user_id)), + deleted_at: Set(None), + version_lock: Set(1), + }; + Ok(active.insert(&self.db).await?) + } + + /// 激活指定 Prompt(停用同 name+category 的其他版本) + pub async fn activate_prompt( + &self, + id: Uuid, + tenant_id: Uuid, + ) -> AiResult { + let entity = ai_prompt::Entity::find_by_id(id) + .one(&self.db) + .await? + .ok_or_else(|| AiError::PromptNotFound(id.to_string()))?; + + if entity.tenant_id != tenant_id { + return Err(AiError::Validation("跨租户操作".into())); + } + + // 停用同 name + category 的其他激活版本 + let siblings = ai_prompt::Entity::find() + .filter(ai_prompt::Column::TenantId.eq(tenant_id)) + .filter(ai_prompt::Column::Name.eq(&entity.name)) + .filter(ai_prompt::Column::Category.eq(&entity.category)) + .filter(ai_prompt::Column::IsActive.eq(true)) + .filter(ai_prompt::Column::DeletedAt.is_null()) + .all(&self.db) + .await?; + + for sibling in siblings { + let mut active: ai_prompt::ActiveModel = sibling.into(); + active.is_active = Set(false); + active.updated_at = Set(chrono::Utc::now()); + active.update(&self.db).await?; + } + + // 激活目标 + let mut active: ai_prompt::ActiveModel = entity.into(); + active.is_active = Set(true); + active.updated_at = Set(chrono::Utc::now()); + Ok(active.update(&self.db).await?) + } + + /// 回滚(= 激活指定旧版本) + pub async fn rollback_prompt( + &self, + id: Uuid, + tenant_id: Uuid, + ) -> AiResult { + self.activate_prompt(id, tenant_id).await + } }