新增: F-01模型拉取IPC+Settings拉取UI(FR-S1密钥内存解析闭环)

This commit is contained in:
2026-06-17 00:14:38 +08:00
parent 8a142c212c
commit a26b86e003
7 changed files with 257 additions and 2 deletions

View File

@@ -6,6 +6,8 @@ use serde::Serialize;
use tauri::{AppHandle, Emitter, State};
use df_ai::provider::ChatMessage;
// df-ai 重导出 df_ai_core(供下游直接引用 trait/类型);src-tauri 不直接依赖 df-ai-core crate。
use df_ai::df_ai_core::model::ModelConfig;
use df_core::types::new_id;
use df_storage::models::AiProviderRecord;
@@ -827,6 +829,78 @@ pub async fn ai_delete_provider(
Ok(())
}
// ============================================================
// 模型列表拉取 + 单模型探测(F-01 阶段5 IPC)
// ============================================================
/// 将前端 provider_type 规范化为 fetch_and_probe 接受的类型。
///
/// Settings.vue 存的 provider_type 是 "openai_compat" / "anthropic"(对齐 build_provider 工厂),
/// 而 model_fetch::fetch_and_probe 分派用 "openai_compat" / "anthropic_compat"。
/// 两个工厂入口类型语义一致(anthropic 协议),仅命名不同,这里收敛归一。
fn normalize_provider_type_for_fetch(provider_type: &str) -> String {
match provider_type {
"anthropic" => "anthropic_compat".to_string(),
other => other.to_string(),
}
}
/// 测试连接并拉取厂商模型列表(F-01 阶段5)
///
/// 流程:
/// 1. DB 取 AiProviderRecord(get_by_id)
/// 2. FR-S1:经 resolve_provider_secret 内存解析真实 api_key(keyring 优先 fallback DB,
/// 绝不进日志/返回值/错误信息)
/// 3. provider_type 归一(anthropic→anthropic_compat)+ base_url + api_key 调
/// df_ai::model_fetch::fetch_and_probe → Vec<ModelConfig>(每个模型名已探测出 4 维度)
/// 4. 写回 AiProviderRecord.model_configs(update_full)→ 返回 Vec<ModelConfig>
///
/// ModelConfig 本就无 api_key 字段,返回值天然不含密钥(FR-S1 闭环)。
#[tauri::command]
pub async fn ai_fetch_models(
state: State<'_, AppState>,
provider_id: String,
) -> Result<Vec<ModelConfig>, String> {
let provider = state
.ai_providers
.get_by_id(&provider_id)
.await
.map_err(err_str)?
.ok_or_else(|| format!("提供商不存在: {}", provider_id))?;
// FR-S1:内存解析密钥,绝不外泄(不入日志/返回值/错误信息)
let api_key = df_storage::secret::resolve_provider_secret(&provider);
let fetch_type = normalize_provider_type_for_fetch(&provider.provider_type);
let configs = df_ai::model_fetch::fetch_and_probe(&fetch_type, &provider.base_url, &api_key)
.await
.map_err(err_str)?;
// 写回 model_configs(更新 updated_at)
let mut updated = provider.clone();
updated.model_configs = configs.clone();
updated.updated_at = now_millis();
state
.ai_providers
.update_full(&updated)
.await
.map_err(err_str)?;
Ok(configs)
}
/// 单模型探测(F-01 阶段5):纯 CPU 启发式 + 预设表,无网络。
///
/// 用途:拉取后用户手动补一个模型名、或想重探某模型的能力维度。
/// 直接调 df_ai::model_probe::probe(&model_id),返回填充了 probe_source 的 ModelConfig。
#[tauri::command]
pub async fn ai_probe_model(
_state: State<'_, AppState>,
model_id: String,
) -> Result<ModelConfig, String> {
Ok(df_ai::model_probe::probe(&model_id))
}
// ============================================================
// 对话管理
// ============================================================

View File

@@ -122,6 +122,9 @@ pub fn run() {
commands::ai::ai_save_provider,
commands::ai::ai_set_provider,
commands::ai::ai_delete_provider,
// F-01 阶段5:测试连接拉取模型列表 + 单模型探测
commands::ai::ai_fetch_models,
commands::ai::ai_probe_model,
// AI 对话管理
commands::ai::ai_conversation_create,
commands::ai::ai_conversation_list,