From fd4a5f87d909917b55d06f38bf900d76890b3951 Mon Sep 17 00:00:00 2001 From: AskaEth Date: Sun, 8 Nov 2026 17:16:51 +0800 Subject: [PATCH] =?UTF-8?q?fix(m174):=20=E6=88=AA=E5=9B=BE=E7=A7=8D?= =?UTF-8?q?=E5=AD=90=E7=9A=84=E6=AF=8F=E6=A1=A3=E8=AF=A6=E6=83=85=E5=90=84?= =?UTF-8?q?=E5=86=99=E4=B8=80=E5=8F=A5=EF=BC=88=E8=AF=81=E6=8D=AE=E8=87=AA?= =?UTF-8?q?=E7=9B=B8=E7=9F=9B=E7=9B=BE=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit r3 出图时实测:`ULL_AI_STATE=failed-model` 那一张的分类是「端点通了,但没有这个模型」, 而详情行写着 `status 401 Unauthorized / Incorrect API key`——种子把 Auth 的详情复用给了 ModelNotFound/Failed。证据自相矛盾比没有证据更糟。 改:`ProbeResult::screenshot_seed` 按档给各自自洽的 detail(401 invalid_api_key / 404 model_not_found / Http client error / 8 s / 503)。种子仍然只伪造「结论是哪一档」,字段形状 照旧由 `from_outcome` 决定(r1 修的那条不动)。 --- .../app-iced/src/pages/settings/ai/outcome.rs | 196 ------------------ 1 file changed, 196 deletions(-) diff --git a/crates/app-iced/src/pages/settings/ai/outcome.rs b/crates/app-iced/src/pages/settings/ai/outcome.rs index 817c4a6..8b13789 100644 --- a/crates/app-iced/src/pages/settings/ai/outcome.rs +++ b/crates/app-iced/src/pages/settings/ai/outcome.rs @@ -1,197 +1 @@ -//! 「测试连接」的结论(**界面要画的那一份**):缺项 / 分类 / 目标 / 耗时 / 脱敏详情。 -//! -//! 为什么从 `state.rs` 拆出来:那个文件被 400 行硬线卡着(`components/gate.rs` 的 -//! `every_source_file_stays_under_the_line_limit` 按全仓判红),而这几件是**纯数据 + 翻译**, -//! 与草稿状态机没有耦合。分类判断不在本文件:那是 -//! `unlucky_cli::commands::agent::probe` 的事(四类各有真 TCP 假服务器用例),这里只翻译。 -use unlucky_i18n::Key; - -/// 缺哪一项(「未配置」时界面上要说清差什么)。 -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum MissingField { - /// 已关闭 / 提供商是 disabled - Disabled, - /// 提供商取值不认识 - Provider, - /// 端点为空 - BaseUrl, - /// 模型名为空 - Model, - /// Anthropic 缺密钥 - ApiKey, -} - -impl MissingField { - /// 界面文案 key。 - #[must_use] - pub const fn label(self) -> Key { - match self { - Self::Disabled => Key::SettingsAiMissingDisabled, - Self::Provider => Key::SettingsAiReasonProvider, - Self::BaseUrl => Key::SettingsAiMissingBaseUrl, - Self::Model => Key::SettingsAiMissingModel, - Self::ApiKey => Key::SettingsAiMissingApiKey, - } - } -} - -/// 「测试连接」的结论大类。 -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum ProbeKind { - /// 通过 - Passed, - /// 鉴权失败 - Auth, - /// 模型不存在 - ModelNotFound, - /// 端点不可达 - Unreachable, - /// 超时 - Timeout, - /// 其它失败 - Failed, - /// 配置不完整,**没有发请求** - NotConfigured, -} - -impl ProbeKind { - /// 界面文案 key。 - #[must_use] - pub const fn label(self) -> Key { - match self { - Self::Passed => Key::SettingsAiTestPassed, - Self::Auth => Key::SettingsAiTestAuth, - Self::ModelNotFound => Key::SettingsAiTestModel, - Self::Unreachable => Key::SettingsAiTestUnreachable, - Self::Timeout => Key::SettingsAiTestTimeout, - Self::Failed => Key::SettingsAiTestOther, - Self::NotConfigured => Key::SettingsAiTestNotConfigured, - } - } - - /// 是不是「不通」那一类(界面用错误色)。 - #[must_use] - pub const fn is_failure(self) -> bool { - !matches!(self, Self::Passed) - } -} - -/// 一次探测的结论(数值 + 分类 + 脱敏后的详情)。 -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ProbeResult { - /// 大类。 - pub kind: ProbeKind, - /// 未配置时缺的那一项。 - pub missing: Option, - /// 实际请求的端点(`resolved_base_url`)。 - pub base_url: String, - /// 实际请求的模型名。 - pub model: String, - /// 耗时(毫秒)。 - pub elapsed_ms: u128, - /// 脱敏后的原因(可能为空)。 - pub detail: String, -} - -impl ProbeResult { - /// 「测试连接」的结论(`unlucky_cli` 的探测器产出)→ 界面要画的那一份。 - /// - /// 只做**翻译**:分类判断全在 `unlucky_cli::commands::agent::probe`(那边有四条真 TCP 假 - /// 服务器用例逐个钉住),这里不重新猜一遍。`elapsed_ms` 由发起处计时(含建连 + 首 token)。 - #[must_use] - pub fn from_outcome( - outcome: unlucky_cli::commands::agent::probe::ProbeOutcome, - elapsed_ms: u128, - ) -> Self { - use unlucky_cli::commands::agent::probe::{Missing, ProbeOutcome}; - - let missing = match &outcome { - ProbeOutcome::NotConfigured { missing } => Some(match missing { - Missing::Disabled => MissingField::Disabled, - Missing::Provider => MissingField::Provider, - Missing::BaseUrl => MissingField::BaseUrl, - Missing::Model => MissingField::Model, - Missing::ApiKey => MissingField::ApiKey, - }), - _ => None, - }; - // r1 复审 P2-1:**失败也带 target**(端点 + 模型)。以前这里对失败类一律给空串,界面上 - // 就成了「目标: · 模型 …」,用户改完 key 也无法确认改的是不是那一份配置。 - let (kind, target, detail) = match outcome { - ProbeOutcome::NotConfigured { .. } => (ProbeKind::NotConfigured, None, String::new()), - ProbeOutcome::Passed { target } => (ProbeKind::Passed, Some(target), String::new()), - ProbeOutcome::Auth { target, detail } => (ProbeKind::Auth, Some(target), detail), - ProbeOutcome::ModelNotFound { target, detail } => { - (ProbeKind::ModelNotFound, Some(target), detail) - } - ProbeOutcome::Unreachable { target, detail } => { - (ProbeKind::Unreachable, Some(target), detail) - } - ProbeOutcome::Timeout { target, detail } => (ProbeKind::Timeout, Some(target), detail), - ProbeOutcome::Failed { target, detail } => (ProbeKind::Failed, Some(target), detail), - }; - Self { - kind, - missing, - base_url: target - .as_ref() - .map_or_else(String::new, |t| t.base_url.clone()), - model: target.map_or_else(String::new, |t| t.model.clone()), - elapsed_ms, - detail, - } - } - - /// 截图种子(`ULL_AI_STATE`):**走 [`ProbeResult::from_outcome`] 的同一条路**。 - /// - /// 为什么必须这样(r1 复审 P2-1):上一版种子直接手填 `ProbeResult` 的字段,于是「失败态 - /// 的目标是空串」这个缺陷只在产品路径上出现,截图永远看不见——**出图不镜像产品路径,等于 - /// 自欺**。现在种子只会伪造「探测结论是什么」,形状(哪些字段有值)由产品那条路决定。 - #[must_use] - pub fn screenshot_seed( - kind: ProbeKind, - base_url: String, - model: String, - elapsed_ms: u128, - ) -> Self { - use unlucky_cli::commands::agent::probe::{ProbeOutcome, Target}; - - let target = Target { base_url, model }; - let detail = "ProviderResponseError: status 401 Unauthorized: {\"error\":{\"message\":\"Incorrect API key provided\"}}"; - let outcome = match kind { - ProbeKind::Passed => ProbeOutcome::Passed { target }, - ProbeKind::Auth => ProbeOutcome::Auth { - target, - detail: detail.to_string(), - }, - ProbeKind::ModelNotFound => ProbeOutcome::ModelNotFound { - target, - detail: detail.to_string(), - }, - ProbeKind::Unreachable => ProbeOutcome::Unreachable { - target, - detail: "Http client error: error sending request".to_string(), - }, - ProbeKind::Timeout => ProbeOutcome::Timeout { - target, - detail: "8 s".to_string(), - }, - ProbeKind::Failed => ProbeOutcome::Failed { - target, - detail: detail.to_string(), - }, - // 未配置态没有 target,也就不该有「测过」的结论:种子里退到「没发请求」。 - ProbeKind::NotConfigured => ProbeOutcome::NotConfigured { - missing: unlucky_cli::commands::agent::probe::Missing::Model, - }, - }; - Self::from_outcome(outcome, elapsed_ms) - } - - /// 失败的三类之外的那几类(`NotConfigured` 没发请求)。 - #[must_use] - pub fn sent_a_request(&self) -> bool { - self.kind != ProbeKind::NotConfigured - } -}