feat(ai): AI 健康管家 V2 基础设施 — 功能开关 + 角色沙箱准备 + 体征页 AI 趋势分析
- 迁移 000153: 新增 ai_feature_flags / ai_usage_daily / ai_suggestion_feedback 三张表, ai_tenant_configs 增加 billing_enabled 列, seed 12 个功能开关 + 2 个管理权限码 - 新增 FeatureFlagService: 5 分钟缓存 + DB 回退 + 即时更新 - VitalSignsTab 添加 AI 趋势分析按钮 (SSE 流式) - 新增 3 个 Entity (ai_feature_flags / ai_usage_daily / ai_suggestion_feedback) - AiState 扩展 feature_flags 字段 - 设计规格 + 讨论记录文档 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -110,6 +110,119 @@ impl ProviderRegistry {
|
||||
pub fn get_provider(&self, name: &str) -> Option<Arc<dyn AiProvider>> {
|
||||
self.entries.get(name).map(|e| e.provider.clone())
|
||||
}
|
||||
|
||||
/// 从 DB 配置重新加载所有 provider(原子替换)
|
||||
///
|
||||
/// 读取 settings 表中的 provider 配置,解密 API Key,
|
||||
/// 构造新的 provider 实例并原子替换 registry 中的条目。
|
||||
/// 跳过未启用的 provider(从 registry 中移除)。
|
||||
pub async fn reload_providers(
|
||||
&self,
|
||||
values: &std::collections::HashMap<String, serde_json::Value>,
|
||||
kek: &[u8; 32],
|
||||
) -> Result<(), String> {
|
||||
use crate::config_resolver::{AiProviderConfig, decrypt_api_key};
|
||||
|
||||
let provider_configs = [
|
||||
("claude", AiProviderConfig::claude_default()),
|
||||
("openai", AiProviderConfig::openai_default()),
|
||||
("ollama", AiProviderConfig::ollama_default()),
|
||||
];
|
||||
|
||||
// 收集需要移除的 provider(不在配置中或未启用)
|
||||
let mut enabled_names = Vec::new();
|
||||
|
||||
for (name, default) in &provider_configs {
|
||||
let enabled_key = format!("ai.provider.{}.enabled", name);
|
||||
let enabled = values
|
||||
.get(&enabled_key)
|
||||
.and_then(|v| v.as_bool())
|
||||
.unwrap_or(default.enabled);
|
||||
|
||||
if !enabled {
|
||||
tracing::info!(provider = name, "Provider 未启用,跳过注册");
|
||||
continue;
|
||||
}
|
||||
|
||||
let base_url_key = format!("ai.provider.{}.base_url", name);
|
||||
let base_url = values
|
||||
.get(&base_url_key)
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or(&default.base_url)
|
||||
.to_string();
|
||||
|
||||
let model_key = format!("ai.provider.{}.model", name);
|
||||
let model = values
|
||||
.get(&model_key)
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or(&default.model)
|
||||
.to_string();
|
||||
|
||||
let api_key = if name != &"ollama" {
|
||||
let api_key_key = format!("ai.provider.{}.api_key", name);
|
||||
let raw = values
|
||||
.get(&api_key_key)
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("");
|
||||
if raw.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
decrypt_api_key(raw, kek).unwrap_or_else(|_| raw.to_string())
|
||||
}
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
match *name {
|
||||
"claude" => {
|
||||
if !api_key.is_empty() {
|
||||
let provider = crate::provider::claude::ClaudeProvider::new(api_key)
|
||||
.with_base_url(base_url);
|
||||
self.register(name.to_string(), Arc::new(provider));
|
||||
enabled_names.push(name.to_string());
|
||||
} else {
|
||||
tracing::warn!("Claude provider 缺少 API Key,跳过注册");
|
||||
}
|
||||
}
|
||||
"openai" => {
|
||||
if !api_key.is_empty() {
|
||||
let provider = crate::provider::openai::OpenAIProvider::new(
|
||||
api_key,
|
||||
base_url,
|
||||
model.clone(),
|
||||
);
|
||||
self.register(name.to_string(), Arc::new(provider));
|
||||
enabled_names.push(name.to_string());
|
||||
} else {
|
||||
tracing::warn!("OpenAI provider 缺少 API Key,跳过注册");
|
||||
}
|
||||
}
|
||||
"ollama" => {
|
||||
let provider =
|
||||
crate::provider::ollama::OllamaProvider::new(base_url, model.clone());
|
||||
self.register(name.to_string(), Arc::new(provider));
|
||||
enabled_names.push(name.to_string());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// 移除未启用的 provider
|
||||
let current_names: Vec<String> = self.provider_names();
|
||||
for name in ¤t_names {
|
||||
if !enabled_names.contains(name) {
|
||||
self.entries.remove(name);
|
||||
tracing::info!(provider = name, "已移除未启用的 provider");
|
||||
}
|
||||
}
|
||||
|
||||
tracing::info!(
|
||||
providers = ?self.provider_names(),
|
||||
"Provider registry 已重新加载"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ResolvedProvider {
|
||||
|
||||
Reference in New Issue
Block a user