feat(ai): 知识库 V2 Handler + 路由注册 + State 初始化
5 个端点:GET/POST /ai/knowledge-bases, GET/PUT/DELETE /ai/knowledge-bases/{id}
AiState 新增 knowledge_v2 字段,main.rs 初始化。
This commit is contained in:
172
crates/erp-ai/src/handler/knowledge_v2_handler.rs
Normal file
172
crates/erp-ai/src/handler/knowledge_v2_handler.rs
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
use axum::Json;
|
||||||
|
use axum::extract::{Extension, FromRef, Path, State};
|
||||||
|
use erp_core::rbac::require_permission;
|
||||||
|
use erp_core::types::{ApiResponse, TenantContext};
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use crate::service::knowledge_v2::{
|
||||||
|
CreateKnowledgeBaseReq, ListKnowledgeBasesQuery, UpdateKnowledgeBaseReq,
|
||||||
|
};
|
||||||
|
use crate::state::AiState;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct ListKnowledgeBasesParams {
|
||||||
|
pub kb_type: Option<String>,
|
||||||
|
pub is_enabled: Option<bool>,
|
||||||
|
pub page: Option<u64>,
|
||||||
|
pub page_size: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/ai/knowledge-bases",
|
||||||
|
responses((status = 200, description = "知识库列表")),
|
||||||
|
tag = "知识库V2",
|
||||||
|
security(("bearer_auth" = [])),
|
||||||
|
)]
|
||||||
|
pub async fn list_knowledge_bases<S>(
|
||||||
|
State(state): State<AiState>,
|
||||||
|
Extension(ctx): Extension<TenantContext>,
|
||||||
|
axum::extract::Query(params): axum::extract::Query<ListKnowledgeBasesParams>,
|
||||||
|
) -> Result<Json<ApiResponse<serde_json::Value>>, erp_core::error::AppError>
|
||||||
|
where
|
||||||
|
AiState: FromRef<S>,
|
||||||
|
S: Clone + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
require_permission(&ctx, "ai.knowledge.list")?;
|
||||||
|
|
||||||
|
let query = ListKnowledgeBasesQuery {
|
||||||
|
kb_type: params.kb_type,
|
||||||
|
is_enabled: params.is_enabled,
|
||||||
|
page: params.page,
|
||||||
|
page_size: params.page_size,
|
||||||
|
};
|
||||||
|
let (items, total) = state.knowledge_v2.list(ctx.tenant_id, &query).await?;
|
||||||
|
|
||||||
|
Ok(Json(ApiResponse::ok(serde_json::json!({
|
||||||
|
"data": items,
|
||||||
|
"total": total,
|
||||||
|
"page": query.page.unwrap_or(1),
|
||||||
|
"page_size": query.page_size.unwrap_or(20),
|
||||||
|
}))))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/ai/knowledge-bases/{id}",
|
||||||
|
responses((status = 200, description = "知识库详情")),
|
||||||
|
tag = "知识库V2",
|
||||||
|
security(("bearer_auth" = [])),
|
||||||
|
)]
|
||||||
|
pub async fn get_knowledge_base<S>(
|
||||||
|
State(state): State<AiState>,
|
||||||
|
Extension(ctx): Extension<TenantContext>,
|
||||||
|
Path(id): Path<uuid::Uuid>,
|
||||||
|
) -> Result<Json<ApiResponse<serde_json::Value>>, erp_core::error::AppError>
|
||||||
|
where
|
||||||
|
AiState: FromRef<S>,
|
||||||
|
S: Clone + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
require_permission(&ctx, "ai.knowledge.list")?;
|
||||||
|
let kb = state.knowledge_v2.get_by_id(ctx.tenant_id, id).await?;
|
||||||
|
Ok(Json(ApiResponse::ok(
|
||||||
|
serde_json::to_value(&kb).unwrap_or_default(),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
post,
|
||||||
|
path = "/ai/knowledge-bases",
|
||||||
|
request_body = CreateKnowledgeBaseReq,
|
||||||
|
responses((status = 200, description = "创建知识库")),
|
||||||
|
tag = "知识库V2",
|
||||||
|
security(("bearer_auth" = [])),
|
||||||
|
)]
|
||||||
|
pub async fn create_knowledge_base<S>(
|
||||||
|
State(state): State<AiState>,
|
||||||
|
Extension(ctx): Extension<TenantContext>,
|
||||||
|
Json(body): Json<CreateKnowledgeBaseReq>,
|
||||||
|
) -> Result<Json<ApiResponse<serde_json::Value>>, erp_core::error::AppError>
|
||||||
|
where
|
||||||
|
AiState: FromRef<S>,
|
||||||
|
S: Clone + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
require_permission(&ctx, "ai.knowledge.manage")?;
|
||||||
|
|
||||||
|
if body.name.trim().is_empty() {
|
||||||
|
return Err(erp_core::error::AppError::Validation(
|
||||||
|
"知识库名称不能为空".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if body.kb_type.trim().is_empty() {
|
||||||
|
return Err(erp_core::error::AppError::Validation(
|
||||||
|
"知识库类型不能为空".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let id = state
|
||||||
|
.knowledge_v2
|
||||||
|
.create(ctx.tenant_id, ctx.user_id, body)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(Json(ApiResponse::ok(serde_json::json!({ "id": id }))))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
put,
|
||||||
|
path = "/ai/knowledge-bases/{id}",
|
||||||
|
request_body = UpdateKnowledgeBaseReq,
|
||||||
|
responses((status = 200, description = "更新知识库")),
|
||||||
|
tag = "知识库V2",
|
||||||
|
security(("bearer_auth" = [])),
|
||||||
|
)]
|
||||||
|
pub async fn update_knowledge_base<S>(
|
||||||
|
State(state): State<AiState>,
|
||||||
|
Extension(ctx): Extension<TenantContext>,
|
||||||
|
Path(id): Path<uuid::Uuid>,
|
||||||
|
Json(body): Json<UpdateKnowledgeBaseReq>,
|
||||||
|
) -> Result<Json<ApiResponse<serde_json::Value>>, erp_core::error::AppError>
|
||||||
|
where
|
||||||
|
AiState: FromRef<S>,
|
||||||
|
S: Clone + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
require_permission(&ctx, "ai.knowledge.manage")?;
|
||||||
|
|
||||||
|
if let Some(ref name) = body.name
|
||||||
|
&& name.trim().is_empty()
|
||||||
|
{
|
||||||
|
return Err(erp_core::error::AppError::Validation(
|
||||||
|
"知识库名称不能为空".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
state
|
||||||
|
.knowledge_v2
|
||||||
|
.update(ctx.tenant_id, ctx.user_id, id, body)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(Json(ApiResponse::ok(serde_json::json!({ "id": id }))))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
delete,
|
||||||
|
path = "/ai/knowledge-bases/{id}",
|
||||||
|
responses((status = 200, description = "删除知识库")),
|
||||||
|
tag = "知识库V2",
|
||||||
|
security(("bearer_auth" = [])),
|
||||||
|
)]
|
||||||
|
pub async fn delete_knowledge_base<S>(
|
||||||
|
State(state): State<AiState>,
|
||||||
|
Extension(ctx): Extension<TenantContext>,
|
||||||
|
Path(id): Path<uuid::Uuid>,
|
||||||
|
) -> Result<Json<ApiResponse<serde_json::Value>>, erp_core::error::AppError>
|
||||||
|
where
|
||||||
|
AiState: FromRef<S>,
|
||||||
|
S: Clone + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
require_permission(&ctx, "ai.knowledge.manage")?;
|
||||||
|
|
||||||
|
state.knowledge_v2.delete(ctx.tenant_id, id).await?;
|
||||||
|
|
||||||
|
Ok(Json(ApiResponse::ok(serde_json::json!({ "id": id }))))
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ pub mod chat_handler;
|
|||||||
pub mod config_handler;
|
pub mod config_handler;
|
||||||
pub mod insight_handler;
|
pub mod insight_handler;
|
||||||
pub mod knowledge_handler;
|
pub mod knowledge_handler;
|
||||||
|
pub mod knowledge_v2_handler;
|
||||||
pub mod risk_handler;
|
pub mod risk_handler;
|
||||||
pub mod rule_handler;
|
pub mod rule_handler;
|
||||||
pub mod suggestion_handler;
|
pub mod suggestion_handler;
|
||||||
|
|||||||
@@ -588,6 +588,27 @@ impl AiModule {
|
|||||||
"/ai/knowledge/guides/{id}/re-embed",
|
"/ai/knowledge/guides/{id}/re-embed",
|
||||||
axum::routing::post(crate::handler::knowledge_handler::re_embed_guide),
|
axum::routing::post(crate::handler::knowledge_handler::re_embed_guide),
|
||||||
)
|
)
|
||||||
|
// 知识库 V2 路由
|
||||||
|
.route(
|
||||||
|
"/ai/knowledge-bases",
|
||||||
|
axum::routing::get(crate::handler::knowledge_v2_handler::list_knowledge_bases),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/ai/knowledge-bases",
|
||||||
|
axum::routing::post(crate::handler::knowledge_v2_handler::create_knowledge_base),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/ai/knowledge-bases/{id}",
|
||||||
|
axum::routing::get(crate::handler::knowledge_v2_handler::get_knowledge_base),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/ai/knowledge-bases/{id}",
|
||||||
|
axum::routing::put(crate::handler::knowledge_v2_handler::update_knowledge_base),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/ai/knowledge-bases/{id}",
|
||||||
|
axum::routing::delete(crate::handler::knowledge_v2_handler::delete_knowledge_base),
|
||||||
|
)
|
||||||
.route(
|
.route(
|
||||||
"/ai/dialysis/risk-assessment",
|
"/ai/dialysis/risk-assessment",
|
||||||
axum::routing::post(crate::handler::assess_dialysis_risk),
|
axum::routing::post(crate::handler::assess_dialysis_risk),
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use crate::service::chat_session::ChatSessionService;
|
|||||||
use crate::service::feature_flag_service::FeatureFlagService;
|
use crate::service::feature_flag_service::FeatureFlagService;
|
||||||
use crate::service::insight_service::InsightService;
|
use crate::service::insight_service::InsightService;
|
||||||
use crate::service::knowledge::KnowledgeService;
|
use crate::service::knowledge::KnowledgeService;
|
||||||
|
use crate::service::knowledge_v2::KnowledgeV2Service;
|
||||||
use crate::service::prompt::PromptService;
|
use crate::service::prompt::PromptService;
|
||||||
use crate::service::quota::QuotaService;
|
use crate::service::quota::QuotaService;
|
||||||
use crate::service::risk_service::RiskService;
|
use crate::service::risk_service::RiskService;
|
||||||
@@ -34,6 +35,7 @@ pub struct AiState {
|
|||||||
pub insight_service: Arc<InsightService>,
|
pub insight_service: Arc<InsightService>,
|
||||||
pub feature_flags: Arc<FeatureFlagService>,
|
pub feature_flags: Arc<FeatureFlagService>,
|
||||||
pub knowledge: Arc<KnowledgeService>,
|
pub knowledge: Arc<KnowledgeService>,
|
||||||
|
pub knowledge_v2: Arc<KnowledgeV2Service>,
|
||||||
pub chat_session: Arc<ChatSessionService>,
|
pub chat_session: Arc<ChatSessionService>,
|
||||||
pub chat_message: Arc<ChatMessageService>,
|
pub chat_message: Arc<ChatMessageService>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -616,6 +616,9 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
erp_ai::service::embedding::EmbeddingService::from_settings(&db).await,
|
erp_ai::service::embedding::EmbeddingService::from_settings(&db).await,
|
||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
|
knowledge_v2: std::sync::Arc::new(
|
||||||
|
erp_ai::service::knowledge_v2::KnowledgeV2Service::new(db.clone()),
|
||||||
|
),
|
||||||
chat_session: std::sync::Arc::new(
|
chat_session: std::sync::Arc::new(
|
||||||
erp_ai::service::chat_session::ChatSessionService::new(db.clone()),
|
erp_ai::service::chat_session::ChatSessionService::new(db.clone()),
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user