后端重写提交
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
use std::{error::Error, fmt};
|
||||
|
||||
use module_ai::{AiTaskService, InMemoryAiTaskStore};
|
||||
use module_auth::{
|
||||
AuthUserService, InMemoryAuthStore, PasswordEntryService, PhoneAuthService,
|
||||
RefreshSessionService, WechatAuthService, WechatAuthStateService,
|
||||
@@ -7,6 +8,7 @@ use module_auth::{
|
||||
use platform_auth::{
|
||||
JwtConfig, JwtError, RefreshCookieConfig, RefreshCookieError, RefreshCookieSameSite,
|
||||
};
|
||||
use platform_llm::{LlmClient, LlmConfig, LlmError};
|
||||
use platform_oss::{OssClient, OssConfig, OssError};
|
||||
use spacetime_client::{SpacetimeClient, SpacetimeClientConfig};
|
||||
|
||||
@@ -29,7 +31,10 @@ pub struct AppState {
|
||||
wechat_auth_state_service: WechatAuthStateService,
|
||||
wechat_auth_service: WechatAuthService,
|
||||
wechat_provider: WechatProvider,
|
||||
#[cfg_attr(not(test), allow(dead_code))]
|
||||
ai_task_service: AiTaskService,
|
||||
spacetime_client: SpacetimeClient,
|
||||
llm_client: Option<LlmClient>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -37,6 +42,7 @@ pub enum AppStateInitError {
|
||||
Jwt(JwtError),
|
||||
RefreshCookie(RefreshCookieError),
|
||||
Oss(OssError),
|
||||
Llm(LlmError),
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
@@ -68,11 +74,14 @@ impl AppState {
|
||||
let wechat_provider = build_wechat_provider(&config);
|
||||
let refresh_session_service =
|
||||
RefreshSessionService::new(auth_store, config.refresh_session_ttl_days);
|
||||
// AI 编排服务当前先挂接内存态 store,后续再按 task table / procedure 接到 SpacetimeDB 真相源。
|
||||
let ai_task_service = AiTaskService::new(InMemoryAiTaskStore::default());
|
||||
let spacetime_client = SpacetimeClient::new(SpacetimeClientConfig {
|
||||
server_url: config.spacetime_server_url.clone(),
|
||||
database: config.spacetime_database.clone(),
|
||||
token: config.spacetime_token.clone(),
|
||||
});
|
||||
let llm_client = build_llm_client(&config)?;
|
||||
|
||||
Ok(Self {
|
||||
config,
|
||||
@@ -86,7 +95,9 @@ impl AppState {
|
||||
wechat_auth_state_service,
|
||||
wechat_auth_service,
|
||||
wechat_provider,
|
||||
ai_task_service,
|
||||
spacetime_client,
|
||||
llm_client,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -130,9 +141,18 @@ impl AppState {
|
||||
&self.wechat_provider
|
||||
}
|
||||
|
||||
#[cfg_attr(not(test), allow(dead_code))]
|
||||
pub fn ai_task_service(&self) -> &AiTaskService {
|
||||
&self.ai_task_service
|
||||
}
|
||||
|
||||
pub fn spacetime_client(&self) -> &SpacetimeClient {
|
||||
&self.spacetime_client
|
||||
}
|
||||
|
||||
pub fn llm_client(&self) -> Option<&LlmClient> {
|
||||
self.llm_client.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for AppStateInitError {
|
||||
@@ -141,6 +161,7 @@ impl fmt::Display for AppStateInitError {
|
||||
Self::Jwt(error) => write!(f, "{error}"),
|
||||
Self::RefreshCookie(error) => write!(f, "{error}"),
|
||||
Self::Oss(error) => write!(f, "{error}"),
|
||||
Self::Llm(error) => write!(f, "{error}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -165,6 +186,12 @@ impl From<OssError> for AppStateInitError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LlmError> for AppStateInitError {
|
||||
fn from(value: LlmError) -> Self {
|
||||
Self::Llm(value)
|
||||
}
|
||||
}
|
||||
|
||||
fn build_oss_client(config: &AppConfig) -> Result<Option<OssClient>, AppStateInitError> {
|
||||
let has_any_oss_field = config.oss_bucket.is_some()
|
||||
|| config.oss_endpoint.is_some()
|
||||
@@ -188,3 +215,65 @@ fn build_oss_client(config: &AppConfig) -> Result<Option<OssClient>, AppStateIni
|
||||
|
||||
Ok(Some(OssClient::new(oss_config)))
|
||||
}
|
||||
|
||||
fn build_llm_client(config: &AppConfig) -> Result<Option<LlmClient>, AppStateInitError> {
|
||||
let Some(api_key) = config
|
||||
.llm_api_key
|
||||
.as_ref()
|
||||
.map(|value| value.trim())
|
||||
.filter(|value| !value.is_empty())
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let llm_config = LlmConfig::new(
|
||||
config.llm_provider,
|
||||
config.llm_base_url.clone(),
|
||||
api_key.to_string(),
|
||||
config.llm_model.clone(),
|
||||
config.llm_request_timeout_ms,
|
||||
config.llm_max_retries,
|
||||
config.llm_retry_backoff_ms,
|
||||
)?;
|
||||
|
||||
Ok(Some(LlmClient::new(llm_config)?))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use module_ai::{AiTaskKind, generate_ai_task_id};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn app_state_exposes_usable_ai_task_service() {
|
||||
let state = AppState::new(AppConfig::default()).expect("state should build");
|
||||
let task_id = generate_ai_task_id(1_713_680_000_000_000);
|
||||
|
||||
let created = state
|
||||
.ai_task_service()
|
||||
.create_task(module_ai::AiTaskCreateInput {
|
||||
task_id: task_id.clone(),
|
||||
task_kind: AiTaskKind::StoryGeneration,
|
||||
owner_user_id: "user_001".to_string(),
|
||||
request_label: "营地开场".to_string(),
|
||||
source_module: "story".to_string(),
|
||||
source_entity_id: Some("storysess_001".to_string()),
|
||||
request_payload_json: Some("{\"scene\":\"camp\"}".to_string()),
|
||||
stages: AiTaskKind::StoryGeneration.default_stage_blueprints(),
|
||||
created_at_micros: 1_713_680_000_000_000,
|
||||
})
|
||||
.expect("ai task should create");
|
||||
|
||||
assert_eq!(created.task_id, task_id);
|
||||
assert_eq!(created.task_kind, AiTaskKind::StoryGeneration);
|
||||
assert_eq!(created.stages.len(), 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn app_state_skips_llm_client_when_api_key_missing() {
|
||||
let state = AppState::new(AppConfig::default()).expect("state should build");
|
||||
|
||||
assert!(state.llm_client().is_none());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user