feat(m174): 助手(AI)提供商配置真接线 + 「测试连接」
设置页新增第 6 组「助手」:提供商四态 / 端点 base_url / 模型名 / API 密钥(默认掩码, 点「显示」才明文)+ 保存 + 测试连接,四类失败(鉴权 / 模型不存在 / 不可达 / 超时)各自 一句文案 + 目标与耗时。 取舍三条: 1. 读写走 `core::setting` 的 schema API(`SettingStore::load` 现读 → `set` → `save`), 不经 `AppCommand::SetSetting`——那条指令是可序列化 DTO,`ai.api_key` 走它等于让明文进 事件流/日志。与 `SettingsBridge::persist` 写外观是同一条做法。 2. 「测试连接」不另写一套 HTTP:`unlucky_cli::commands::agent::probe` 现读设置文件、 交给 `RigProvider`(真 provider)发一次最小补全,带 8 秒时限;分类只看错误文本 (rig 只给 Display),四类各有一条真 TCP 假服务器用例钉住。 3. `ProviderKind` 补 `from_setting` / `setting_value`:serde 的 `rename_all = snake_case` 产出 `open_ai_compatible`,与 schema 的 `openai_compatible` 不是同一个词——这正是 M174 之前「agent 侧一次都没读过设置」的根因。 守卫(都做变异验证,见 mission 报告):密钥不出 `Debug`/`detail`/DTO;未配置时工厂构造 计数为 0;改完 base_url 后探测打到新地址;空/非法值被 schema 拦下且文件不动。 i18n:49 条中英各一(金表按 `Key::ALL` 位置插入);presenter 的 ai_group 补上 schema 允许 的 `disabled` 选项与一行「已配置 N 字符」的只读状态(仍然不显示值)。
This commit is contained in:
1 parent
82f3bbbff6
commit
9cb605f316
23 files changed
+2939
-73
No files matched your search
@@ -1,14 +1,12 @@
|
||||
//! 设置页分组:按 `core::setting` schema 的键生成行,写回走 `SettingStore::set`。
|
||||
//!
|
||||
//! 三件纪律(AGENTS §6):
|
||||
//! 1. 读写同一份 `<数据根>/config/launcher.json`(与 `ull config set` / AI `set_config` 同源);
|
||||
//! 2. 不自己拼 JSON,也不绕 schema(非法键/越界值由 `store.set` 拒绝);
|
||||
//! 3. 这里只认「键名 + 字符串值」,领域语义留在 core。
|
||||
//! 三件纪律(AGENTS §6):① 读写同一份 `<数据根>/config/launcher.json`(与 `ull config set` /
|
||||
//! AI `set_config` 同源);② 不自己拼 JSON、不绕 schema(非法键/越界值由 `store.set` 拒绝);
|
||||
//! ③ 这里只认「键名 + 字符串值」,领域语义留在 core。
|
||||
//!
|
||||
//! 分组 id 与设置页子导航同名:`download` / `java` / `data` / `ai` / `mcp`。
|
||||
|
||||
use serde_json::Value;
|
||||
use unlucky_core::game::java::JdkVendor;
|
||||
use unlucky_core::setting::spec::ValueKind;
|
||||
|
||||
use crate::dto::{SettingGroupView, SettingRowView};
|
||||
@@ -243,10 +241,22 @@ fn data_group(env: &HostEnv) -> SettingGroupView {
|
||||
))
|
||||
}
|
||||
|
||||
/// AI(D4):开关/端点/模型/YOLO。
|
||||
/// AI(D4):开关/端点/模型/密钥状态/YOLO。
|
||||
/// **选项表直接取 schema 的 `AI_PROVIDERS`**(M174):原先手写三项、漏了 `disabled`,于是被别的
|
||||
/// 写者设成 `disabled` 的机器在这一页会显示成 `openai_compatible`(`selected()` 找不到就回 0)。
|
||||
/// **密钥只显示状态不显示值**:`ai.api_key` 是 `locked` 凭据,进 DTO 就等于进事件流/日志。
|
||||
fn ai_group(env: &HostEnv) -> SettingGroupView {
|
||||
let providers = ["openai_compatible", "anthropic", "ollama"];
|
||||
let providers = unlucky_core::setting::keys::AI_PROVIDERS;
|
||||
let provider = text(env, "ai.provider");
|
||||
let chosen = if provider.is_empty() {
|
||||
"openai_compatible"
|
||||
} else {
|
||||
provider.as_str()
|
||||
};
|
||||
let key_state = match text(env, "ai.api_key").trim().chars().count() {
|
||||
0 => "未配置(在 iced 设置页填写)".to_string(),
|
||||
chars => format!("已配置({chars} 个字符)"),
|
||||
};
|
||||
SettingGroupView::new("ai", "AI 助手(D4)")
|
||||
.with(SettingRowView::toggle(
|
||||
"ai.enabled",
|
||||
@@ -258,15 +268,8 @@ fn ai_group(env: &HostEnv) -> SettingGroupView {
|
||||
"ai.provider",
|
||||
"模型端点",
|
||||
"OpenAI 兼容(含本地 llama.cpp)/ Anthropic / Ollama",
|
||||
&providers,
|
||||
selected(
|
||||
&providers,
|
||||
if provider.is_empty() {
|
||||
"openai_compatible"
|
||||
} else {
|
||||
&provider
|
||||
},
|
||||
),
|
||||
providers,
|
||||
selected(providers, chosen),
|
||||
))
|
||||
.with(SettingRowView::text(
|
||||
"ai.base_url",
|
||||
@@ -280,6 +283,7 @@ fn ai_group(env: &HostEnv) -> SettingGroupView {
|
||||
"如 deepseek-chat",
|
||||
&text(env, "ai.model"),
|
||||
))
|
||||
.with(SettingRowView::read_only("API 密钥", &key_state))
|
||||
.with(SettingRowView::toggle(
|
||||
"ai.yolo",
|
||||
"YOLO 模式",
|
||||
@@ -338,11 +342,8 @@ pub fn set(env: &HostEnv, key: &str, value: &str) -> Result<(), String> {
|
||||
|
||||
/// 键 → 正确类型的 JSON 值(schema 会再校验范围/枚举)。
|
||||
///
|
||||
/// **按 schema 的 `ValueKind` 取类型,不硬编码键名**(复审 P1-1):原先是「硬编码 Int/Bool
|
||||
/// 键名,其余当字符串」,于是 `appearance.window_opacity`(Float)被当 String 送进 `store.set`,
|
||||
/// schema 的 `as_f64()` 返回 `None` ⇒ `InvalidValue` ⇒ **`save()` 不执行**(重启回旧值 + 弹 toast)。
|
||||
/// 现在新增任何 Float/Int/Bool 键都自动正确;集合级守门是
|
||||
/// `every_float_key_in_the_schema_can_be_written_back`。
|
||||
/// **按 schema 的 `ValueKind` 取类型,不硬编码键名**(复审 P1-1):硬编码会让 Float 键被当 String
|
||||
/// 送进 `store.set` ⇒ `InvalidValue` ⇒ `save()` 不执行。守门:`every_float_key_in_the_schema_can_be_written_back`。
|
||||
fn parse_value(key: &str, value: &str) -> Result<Value, String> {
|
||||
let schema = unlucky_core::setting::keys::launcher_schema();
|
||||
let Some(spec) = schema.spec(key) else {
|
||||
@@ -391,7 +392,6 @@ pub fn clear_vendor_priority(env: &HostEnv) -> Result<(), String> {
|
||||
.set("java.vendorPriority", Value::Array(Vec::new()))
|
||||
.and_then(|()| store.save())
|
||||
.map_err(|error| format!("清空发行版优先级失败:{error}"))?;
|
||||
let _ = JdkVendor::ALL;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user