From fada33101cff6348d56bc45750a14f11133257fe Mon Sep 17 00:00:00 2001 From: iven Date: Sat, 25 Apr 2026 13:58:33 +0800 Subject: [PATCH] =?UTF-8?q?feat(ai):=20AiState=20+=20AiModule=20(ErpModule?= =?UTF-8?q?=20impl=20+=20=E6=9D=83=E9=99=90=20+=20=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E9=AA=A8=E6=9E=B6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 --- crates/erp-ai/src/handler/mod.rs | 20 ++++++ crates/erp-ai/src/lib.rs | 5 ++ crates/erp-ai/src/module.rs | 108 +++++++++++++++++++++++++++++++ crates/erp-ai/src/state.rs | 17 +++++ 4 files changed, 150 insertions(+) create mode 100644 crates/erp-ai/src/handler/mod.rs create mode 100644 crates/erp-ai/src/module.rs create mode 100644 crates/erp-ai/src/state.rs diff --git a/crates/erp-ai/src/handler/mod.rs b/crates/erp-ai/src/handler/mod.rs new file mode 100644 index 0000000..352549b --- /dev/null +++ b/crates/erp-ai/src/handler/mod.rs @@ -0,0 +1,20 @@ +use axum::response::IntoResponse; + +pub async fn stream_lab_report() -> impl IntoResponse { + "stub" +} +pub async fn stream_trends() -> impl IntoResponse { + "stub" +} +pub async fn stream_checkup_plan() -> impl IntoResponse { + "stub" +} +pub async fn stream_report_summary() -> impl IntoResponse { + "stub" +} +pub async fn list_analysis() -> impl IntoResponse { + "stub" +} +pub async fn get_analysis() -> impl IntoResponse { + "stub" +} diff --git a/crates/erp-ai/src/lib.rs b/crates/erp-ai/src/lib.rs index c83f9c1..b6ea311 100644 --- a/crates/erp-ai/src/lib.rs +++ b/crates/erp-ai/src/lib.rs @@ -1,9 +1,14 @@ pub mod dto; pub mod entity; pub mod error; +pub mod handler; +pub mod module; pub mod prompt; pub mod provider; pub mod sanitization; pub mod service; +pub mod state; pub use error::{AiError, AiResult}; +pub use module::AiModule; +pub use state::AiState; diff --git a/crates/erp-ai/src/module.rs b/crates/erp-ai/src/module.rs new file mode 100644 index 0000000..7be464b --- /dev/null +++ b/crates/erp-ai/src/module.rs @@ -0,0 +1,108 @@ +use async_trait::async_trait; +use axum::Router; +use erp_core::module::{ErpModule, ModuleType, PermissionDescriptor}; +use std::any::Any; + +pub struct AiModule; + +#[async_trait] +impl ErpModule for AiModule { + fn name(&self) -> &str { + "ai" + } + + fn module_type(&self) -> ModuleType { + ModuleType::Builtin + } + + fn dependencies(&self) -> Vec<&str> { + vec!["health"] + } + + fn permissions(&self) -> Vec { + vec![ + PermissionDescriptor { + code: "ai.analysis.list".into(), + name: "查看分析历史".into(), + description: "查看 AI 分析结果历史记录".into(), + module: "ai".into(), + }, + PermissionDescriptor { + code: "ai.analysis.manage".into(), + name: "请求分析".into(), + description: "发起 AI 分析请求".into(), + module: "ai".into(), + }, + PermissionDescriptor { + code: "ai.prompt.list".into(), + name: "查看 Prompt".into(), + description: "查看 AI Prompt 模板列表".into(), + module: "ai".into(), + }, + PermissionDescriptor { + code: "ai.prompt.manage".into(), + name: "管理 Prompt".into(), + description: "创建/编辑/激活/回滚 Prompt 模板".into(), + module: "ai".into(), + }, + PermissionDescriptor { + code: "ai.usage.list".into(), + name: "查看用量".into(), + description: "查看 AI 用量统计".into(), + module: "ai".into(), + }, + PermissionDescriptor { + code: "ai.provider.manage".into(), + name: "管理提供商".into(), + description: "管理 AI 提供商配置".into(), + module: "ai".into(), + }, + ] + } + + fn as_any(&self) -> &dyn Any { + self + } +} + +impl AiModule { + pub fn public_routes() -> Router + where + crate::state::AiState: axum::extract::FromRef, + S: Clone + Send + Sync + 'static, + { + Router::new() + } + + pub fn protected_routes() -> Router + where + crate::state::AiState: axum::extract::FromRef, + S: Clone + Send + Sync + 'static, + { + Router::new() + .route( + "/ai/analyze/lab-report", + axum::routing::post(crate::handler::stream_lab_report), + ) + .route( + "/ai/analyze/trends", + axum::routing::post(crate::handler::stream_trends), + ) + .route( + "/ai/analyze/checkup-plan", + axum::routing::post(crate::handler::stream_checkup_plan), + ) + .route( + "/ai/analyze/report-summary", + axum::routing::post(crate::handler::stream_report_summary), + ) + .route( + "/ai/analysis/history", + axum::routing::get(crate::handler::list_analysis), + ) + .route( + "/ai/analysis/{id}", + axum::routing::get(crate::handler::get_analysis), + ) + } +} diff --git a/crates/erp-ai/src/state.rs b/crates/erp-ai/src/state.rs new file mode 100644 index 0000000..d6f7cb9 --- /dev/null +++ b/crates/erp-ai/src/state.rs @@ -0,0 +1,17 @@ +use std::sync::Arc; + +use erp_core::events::EventBus; +use sea_orm::DatabaseConnection; + +use crate::service::analysis::AnalysisService; +use crate::service::prompt::PromptService; +use crate::service::usage::UsageService; + +#[derive(Clone)] +pub struct AiState { + pub db: DatabaseConnection, + pub event_bus: EventBus, + pub analysis: Arc, + pub prompt: Arc, + pub usage: Arc, +}