feat(ai): Day 7 — 会话持久化 Entity + Service
- 新增 3 个 SeaORM Entity: ai_chat_session / ai_chat_message / ai_tool_call_log - ChatSessionService: create / list / get / close / rename - ChatMessageService: save_message / list_messages / save_tool_call_log - 参数封装为 SaveMessageParams / SaveToolCallLogParams 避免 clippy too_many_arguments - AiState 注册 chat_session + chat_message 服务 - erp-server main.rs 初始化注入
This commit is contained in:
105
crates/erp-ai/src/service/chat_message.rs
Normal file
105
crates/erp-ai/src/service/chat_message.rs
Normal file
@@ -0,0 +1,105 @@
|
||||
use sea_orm::{
|
||||
ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, QueryOrder,
|
||||
QuerySelect, Set,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::entity::ai_chat_message;
|
||||
use crate::entity::ai_tool_call_log;
|
||||
|
||||
pub struct ChatMessageService {
|
||||
db: DatabaseConnection,
|
||||
}
|
||||
|
||||
pub struct SaveMessageParams {
|
||||
pub tenant_id: Uuid,
|
||||
pub session_id: Uuid,
|
||||
pub role: String,
|
||||
pub content: Option<String>,
|
||||
pub tool_calls: Option<serde_json::Value>,
|
||||
pub tool_call_id: Option<String>,
|
||||
pub token_count: Option<i32>,
|
||||
pub user_id: Uuid,
|
||||
}
|
||||
|
||||
pub struct SaveToolCallLogParams {
|
||||
pub tenant_id: Uuid,
|
||||
pub session_id: Uuid,
|
||||
pub message_id: Uuid,
|
||||
pub tool_name: String,
|
||||
pub parameters: Option<serde_json::Value>,
|
||||
pub result_summary: Option<String>,
|
||||
pub execution_ms: i32,
|
||||
pub success: bool,
|
||||
pub user_id: Uuid,
|
||||
}
|
||||
|
||||
impl ChatMessageService {
|
||||
pub fn new(db: DatabaseConnection) -> Self {
|
||||
Self { db }
|
||||
}
|
||||
|
||||
pub async fn save_message(
|
||||
&self,
|
||||
params: SaveMessageParams,
|
||||
) -> Result<ai_chat_message::Model, sea_orm::DbErr> {
|
||||
let id = Uuid::now_v7();
|
||||
let now = chrono::Utc::now();
|
||||
let model = ai_chat_message::ActiveModel {
|
||||
id: Set(id),
|
||||
tenant_id: Set(params.tenant_id),
|
||||
session_id: Set(params.session_id),
|
||||
role: Set(params.role),
|
||||
content: Set(params.content),
|
||||
tool_calls: Set(params.tool_calls),
|
||||
tool_call_id: Set(params.tool_call_id),
|
||||
token_count: Set(params.token_count),
|
||||
created_at: Set(now),
|
||||
updated_at: Set(now),
|
||||
created_by: Set(Some(params.user_id)),
|
||||
updated_by: Set(Some(params.user_id)),
|
||||
deleted_at: Set(None),
|
||||
version_lock: Set(1),
|
||||
};
|
||||
let result = model.insert(&self.db).await?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn list_messages(
|
||||
&self,
|
||||
tenant_id: Uuid,
|
||||
session_id: Uuid,
|
||||
limit: u64,
|
||||
) -> Result<Vec<ai_chat_message::Model>, sea_orm::DbErr> {
|
||||
ai_chat_message::Entity::find()
|
||||
.filter(ai_chat_message::Column::TenantId.eq(tenant_id))
|
||||
.filter(ai_chat_message::Column::SessionId.eq(session_id))
|
||||
.filter(ai_chat_message::Column::DeletedAt.is_null())
|
||||
.order_by_asc(ai_chat_message::Column::CreatedAt)
|
||||
.limit(limit)
|
||||
.all(&self.db)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn save_tool_call_log(
|
||||
&self,
|
||||
params: SaveToolCallLogParams,
|
||||
) -> Result<ai_tool_call_log::Model, sea_orm::DbErr> {
|
||||
let id = Uuid::now_v7();
|
||||
let model = ai_tool_call_log::ActiveModel {
|
||||
id: Set(id),
|
||||
tenant_id: Set(params.tenant_id),
|
||||
session_id: Set(params.session_id),
|
||||
message_id: Set(params.message_id),
|
||||
tool_name: Set(params.tool_name),
|
||||
parameters: Set(params.parameters),
|
||||
result_summary: Set(params.result_summary),
|
||||
execution_ms: Set(Some(params.execution_ms)),
|
||||
success: Set(params.success),
|
||||
created_at: Set(chrono::Utc::now()),
|
||||
created_by: Set(Some(params.user_id)),
|
||||
};
|
||||
let result = model.insert(&self.db).await?;
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
101
crates/erp-ai/src/service/chat_session.rs
Normal file
101
crates/erp-ai/src/service/chat_session.rs
Normal file
@@ -0,0 +1,101 @@
|
||||
use sea_orm::{
|
||||
ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, QueryOrder, Set,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::entity::ai_chat_session;
|
||||
|
||||
pub struct ChatSessionService {
|
||||
db: DatabaseConnection,
|
||||
}
|
||||
|
||||
impl ChatSessionService {
|
||||
pub fn new(db: DatabaseConnection) -> Self {
|
||||
Self { db }
|
||||
}
|
||||
|
||||
pub async fn create(
|
||||
&self,
|
||||
tenant_id: Uuid,
|
||||
user_id: Uuid,
|
||||
patient_id: Option<Uuid>,
|
||||
title: Option<String>,
|
||||
) -> Result<ai_chat_session::Model, sea_orm::DbErr> {
|
||||
let id = Uuid::now_v7();
|
||||
let now = chrono::Utc::now();
|
||||
let model = ai_chat_session::ActiveModel {
|
||||
id: Set(id),
|
||||
tenant_id: Set(tenant_id),
|
||||
user_id: Set(user_id),
|
||||
patient_id: Set(patient_id),
|
||||
title: Set(title),
|
||||
status: Set("active".to_string()),
|
||||
metadata: 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),
|
||||
};
|
||||
let result = model.insert(&self.db).await?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn list(
|
||||
&self,
|
||||
tenant_id: Uuid,
|
||||
user_id: Uuid,
|
||||
) -> Result<Vec<ai_chat_session::Model>, sea_orm::DbErr> {
|
||||
ai_chat_session::Entity::find()
|
||||
.filter(ai_chat_session::Column::TenantId.eq(tenant_id))
|
||||
.filter(ai_chat_session::Column::UserId.eq(user_id))
|
||||
.filter(ai_chat_session::Column::DeletedAt.is_null())
|
||||
.filter(ai_chat_session::Column::Status.ne("closed"))
|
||||
.order_by_desc(ai_chat_session::Column::UpdatedAt)
|
||||
.all(&self.db)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get(
|
||||
&self,
|
||||
tenant_id: Uuid,
|
||||
session_id: Uuid,
|
||||
) -> Result<Option<ai_chat_session::Model>, sea_orm::DbErr> {
|
||||
ai_chat_session::Entity::find()
|
||||
.filter(ai_chat_session::Column::TenantId.eq(tenant_id))
|
||||
.filter(ai_chat_session::Column::Id.eq(session_id))
|
||||
.filter(ai_chat_session::Column::DeletedAt.is_null())
|
||||
.one(&self.db)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn close(&self, tenant_id: Uuid, session_id: Uuid) -> Result<bool, sea_orm::DbErr> {
|
||||
let session = self.get(tenant_id, session_id).await?;
|
||||
let Some(session) = session else {
|
||||
return Ok(false);
|
||||
};
|
||||
let mut active: ai_chat_session::ActiveModel = session.into();
|
||||
active.status = Set("closed".to_string());
|
||||
active.updated_at = Set(chrono::Utc::now());
|
||||
active.update(&self.db).await?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub async fn rename(
|
||||
&self,
|
||||
tenant_id: Uuid,
|
||||
session_id: Uuid,
|
||||
new_title: String,
|
||||
) -> Result<bool, sea_orm::DbErr> {
|
||||
let session = self.get(tenant_id, session_id).await?;
|
||||
let Some(session) = session else {
|
||||
return Ok(false);
|
||||
};
|
||||
let mut active: ai_chat_session::ActiveModel = session.into();
|
||||
active.title = Set(Some(new_title));
|
||||
active.updated_at = Set(chrono::Utc::now());
|
||||
active.update(&self.db).await?;
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@ pub mod analysis;
|
||||
pub mod analysis_queue;
|
||||
pub mod auto_analysis;
|
||||
pub mod cache;
|
||||
pub mod chat_message;
|
||||
pub mod chat_session;
|
||||
pub mod comparison;
|
||||
pub mod cost;
|
||||
pub mod dialysis_risk_scorer;
|
||||
|
||||
Reference in New Issue
Block a user