Files
Unlucky-Launcher/crates/app-iced/src/pages/settings/ai_about.rs
T
AskaEth 9cb605f316 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 字符」的只读状态(仍然不显示值)。
2026-11-08 15:57:25 +08:00

76 lines
3.2 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 设置页「助手(AI)」分组(M174):左列第 6 组的**挂载点**与本线的消息口。
//!
//! 本文件只留三样东西,正文/状态/读写都在 `pages/settings/ai/` 里:
//!
//! | 这里 | 内容 |
//! |---|---|
//! | [`GROUP`] | 左列名 / 图标 / 演示行(产品路径走 [`super::ai::view::body`]) |
//! | [`AI_GROUP`] | 本组在 `GROUPS` 里的下标(与 General / Update 同一条**按下标分发**:本组要 i18n 文案与 `Lang` 形参,`Group.body` 的签名里没有它) |
//! | [`Message`] | 本线的消息口(落点 `app/update/settings/ai_about.rs`) |
//! | [`State`] | 转发 `ai::State`(`Groups.ai_about` 的载荷类型不变,`settings/state.rs` 一行不用改) |
//!
//! ## 为什么 `body` 是 `None` 而不是 `Some(..)`
//!
//! `Group.body` 的签名是 `fn(&Ui, States) -> Option<Element>`:拿不到 `Lang`,也就画不出中英两套
//! 文案(本组的硬要求是「不得硬编码中文」)。M106 的语言行与 M159 的更新组是同一个理由走了
//! 按下标分发;本组沿用同一条路,代价是 `settings/mod.rs` 里多一行 `else if`。
//!
//! ## 密钥纪律
//!
//! [`Message::EditKey`] 的载荷是用户刚敲的明文(输入框必须有它),它**只**活在这个进程里:
//! 落盘走 `ai::store::save`(不经 `AppCommand::SetSetting` 那条可序列化通道),
//! 上屏默认掩码(`State::reveal` 才明文),`Debug` 由 `ai::state::Secret` 掩码。
pub use super::ai::State;
use unlucky_design::icons::IconKind;
use super::Group;
/// 本组在 [`super::GROUPS`] 里的下标(**必须是最后一个**:加组只追加不重排)。
pub(crate) const AI_GROUP: usize = 5;
/// 「助手」分组(左列名固定英文:左列是导航,与导航轨同源,不进 catalog)。
pub(super) const GROUP: Group = Group {
name: "Assistant",
icon: IconKind::Ai,
// 只给 harness / 窄窗「按组堆叠」路径用的演示行:产品路径走 `ai::view::body`。
rows: &[
("Provider", "OpenAI compatible"),
("Endpoint", "https://api.deepseek.com/v1"),
("Model", "deepseek-chat"),
],
body: None,
};
/// 本线的消息(**只归本线**):加变体只改本文件 + `app/update/settings/ai_about.rs`。
#[derive(Debug, Clone)]
pub enum Message {
/// 启用开关(`ai.enabled`)
ToggleEnabled(bool),
/// 选提供商(`ai.provider`)
PickProvider(super::ai::ProviderId),
/// 端点草稿(`ai.base_url`)
EditBaseUrl(String),
/// 模型名草稿(`ai.model`)
EditModel(String),
/// 新密钥草稿(明文只在内存里;空 = 保留已存的那把)
EditKey(String),
/// 密钥输入框明文/掩码
Reveal(bool),
/// 保存(校验 + 落盘)
Save,
/// 测试连接(异步;结果走 [`Message::ProbeDone`])
Test,
/// 测试连接的结果(分类 + 数值 + 脱敏详情)
ProbeDone(Box<super::ai::ProbeResult>),
}
impl Message {
/// 一条本组消息 → 设置页的投递口(`pages::settings::GroupMessage::AiAbout`)。
#[must_use]
pub fn into_group(self) -> super::GroupMessage {
super::GroupMessage::AiAbout(self)
}
}