190 lines
6.4 KiB
Rust
190 lines
6.4 KiB
Rust
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,
|
||
}
|
||
|
||
impl PromptService {
|
||
pub fn new(db: sea_orm::DatabaseConnection) -> Self {
|
||
Self { db }
|
||
}
|
||
|
||
/// 获取当前激活的 Prompt 模板
|
||
pub async fn get_active_prompt(
|
||
&self,
|
||
tenant_id: Uuid,
|
||
name: &str,
|
||
) -> AiResult<ai_prompt::Model> {
|
||
ai_prompt::Entity::find()
|
||
.filter(ai_prompt::Column::TenantId.eq(tenant_id))
|
||
.filter(ai_prompt::Column::Name.eq(name))
|
||
.filter(ai_prompt::Column::IsActive.eq(true))
|
||
.filter(ai_prompt::Column::DeletedAt.is_null())
|
||
.one(&self.db)
|
||
.await?
|
||
.ok_or_else(|| AiError::PromptNotFound(name.into()))
|
||
}
|
||
|
||
/// 新建 Prompt
|
||
pub async fn create_prompt(
|
||
&self,
|
||
tenant_id: Uuid,
|
||
user_id: Uuid,
|
||
name: String,
|
||
system_prompt: String,
|
||
user_prompt_template: String,
|
||
model_config: serde_json::Value,
|
||
category: String,
|
||
) -> AiResult<ai_prompt::Model> {
|
||
let id = Uuid::now_v7();
|
||
let now = chrono::Utc::now();
|
||
let active = ai_prompt::ActiveModel {
|
||
id: Set(id),
|
||
tenant_id: Set(tenant_id),
|
||
name: Set(name),
|
||
description: Set(String::new()),
|
||
system_prompt: Set(system_prompt),
|
||
user_prompt_template: Set(user_prompt_template),
|
||
variables_schema: Set(None),
|
||
model_config: Set(model_config),
|
||
version: Set(1),
|
||
is_active: Set(true),
|
||
category: Set(category),
|
||
tags: Set(None),
|
||
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 模板
|
||
pub async fn list_prompts(
|
||
&self,
|
||
tenant_id: Uuid,
|
||
category: Option<String>,
|
||
pagination: &Pagination,
|
||
) -> AiResult<(Vec<ai_prompt::Model>, 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<String>,
|
||
user_prompt_template: Option<String>,
|
||
model_config: Option<serde_json::Value>,
|
||
description: Option<String>,
|
||
) -> AiResult<ai_prompt::Model> {
|
||
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<ai_prompt::Model> {
|
||
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<ai_prompt::Model> {
|
||
self.activate_prompt(id, tenant_id).await
|
||
}
|
||
}
|