From e847f1357835a23fd4e9aef0909092ce8fccaca6 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 26 Jun 2026 02:02:28 +0000 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20models.dev=20=E6=8B=89=E5=8F=96?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5=E5=90=8E=20UI=20=E6=B0=B8=E4=B9=85=E5=8D=A1?= =?UTF-8?q?=E5=9C=A8=20loading=20=E6=80=81=20(Fixes=20#145)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因:ai_page.rs:3405 的 EnsureModelsDevLoaded/RefreshModelsDev Err 分支 仅执行 log::warn!,既未调用 ctx.notify() 触发重绘,也未更新任何状态, 导致 cached() 始终返回 None,agent_providers_widget.rs 的 None 分支 永远渲染 "loading" 占位文字,用户无法感知失败也无法恢复。 修复: - models_dev.rs: 新增 FETCH_FAILED AtomicBool + last_fetch_failed() / set_fetch_failed() 公共接口,与现有 CHIPS_EXPANDED 模式一致 - ai_page.rs: EnsureModelsDevLoaded 与 RefreshModelsDev 的 Err 分支 改为调用 set_fetch_failed(true) + ctx.notify(),确保 UI 重绘 - agent_providers_widget.rs: None 分支检查 last_fetch_failed(), 失败时显示含重试提示的文字(catalog-empty),而非误导性的 loading 文字 (页首 Refresh 按钮始终可见,用户可直接点击重试) Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_017po385dpt8y1iuhpDixXbT --- app/src/ai/agent_providers/models_dev.rs | 14 ++++++++++++++ app/src/settings_view/agent_providers_widget.rs | 7 ++++++- app/src/settings_view/ai_page.rs | 12 ++++++++++-- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/app/src/ai/agent_providers/models_dev.rs b/app/src/ai/agent_providers/models_dev.rs index 2371b9ca67..26be1086ab 100644 --- a/app/src/ai/agent_providers/models_dev.rs +++ b/app/src/ai/agent_providers/models_dev.rs @@ -269,6 +269,20 @@ pub fn toggle_chips_expanded() { CHIPS_EXPANDED.fetch_xor(true, Ordering::Relaxed); } +// ── 最近一次网络拉取失败标志 ───────────────────────────────────────────────── + +static FETCH_FAILED: AtomicBool = AtomicBool::new(false); + +/// 最近一次网络拉取是否失败(cached() == None 时有意义)。 +pub fn last_fetch_failed() -> bool { + FETCH_FAILED.load(Ordering::Relaxed) +} + +/// 由调用方在 spawn 回调中设置(失败 true,成功不需要重置,因为 cached() 此时为 Some)。 +pub fn set_fetch_failed(failed: bool) { + FETCH_FAILED.store(failed, Ordering::Relaxed); +} + // ── 快速添加 chip 行的搜索过滤 ────────────────────────────────────────────── fn search_state() -> &'static RwLock { diff --git a/app/src/settings_view/agent_providers_widget.rs b/app/src/settings_view/agent_providers_widget.rs index 10778a0ee1..6e989c7614 100644 --- a/app/src/settings_view/agent_providers_widget.rs +++ b/app/src/settings_view/agent_providers_widget.rs @@ -1462,10 +1462,15 @@ impl AgentProvidersWidget { match models_dev::cached() { None => { + let catalog_text = if models_dev::last_fetch_failed() { + crate::t!("settings-agent-providers-catalog-empty") + } else { + crate::t!("settings-agent-providers-loading-catalog") + }; body.add_child( Container::new( Text::new( - crate::t!("settings-agent-providers-loading-catalog"), + catalog_text, appearance.ui_font_family(), appearance.ui_font_size(), ) diff --git a/app/src/settings_view/ai_page.rs b/app/src/settings_view/ai_page.rs index 9081c7e7c7..44626805ba 100644 --- a/app/src/settings_view/ai_page.rs +++ b/app/src/settings_view/ai_page.rs @@ -3402,7 +3402,11 @@ impl TypedActionView for AISettingsPageView { async move { models_dev::fetch_and_cache(client).await }, |view, result, ctx| match result { Ok(()) => view.rebuild_current_page(ctx), - Err(e) => log::warn!("[models.dev] 拉取失败: {e}"), + Err(e) => { + log::warn!("[models.dev] 拉取失败: {e}"); + models_dev::set_fetch_failed(true); + ctx.notify(); + } }, ); } else { @@ -3416,7 +3420,11 @@ impl TypedActionView for AISettingsPageView { async move { models_dev::fetch_and_cache(client).await }, |view, result, ctx| match result { Ok(()) => view.rebuild_current_page(ctx), - Err(e) => log::warn!("[models.dev] 刷新失败: {e}"), + Err(e) => { + log::warn!("[models.dev] 刷新失败: {e}"); + models_dev::set_fetch_failed(true); + ctx.notify(); + } }, ); } From 6aa836b6a4d291591fbf86386b50f31c600357b8 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 26 Jun 2026 02:11:55 +0000 Subject: [PATCH 2/2] fix: address review comments on PR #285 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ai_page.rs: Ok(()) 分支同步调用 set_fetch_failed(false),让标志严格 反映最近一次拉取结果,避免 catalog 未来被重置为 None 时显示错误失败态 - i18n: 新增专用 key settings-agent-providers-catalog-load-failed(en/zh-CN/ja), 语义明确为"拉取失败",不再复用"目录为空"的 catalog-empty key - agent_providers_widget.rs: 失败态改用新 key,消除语义歧义 Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_017po385dpt8y1iuhpDixXbT --- app/i18n/en/warp.ftl | 1 + app/i18n/ja/warp.ftl | 1 + app/i18n/zh-CN/warp.ftl | 1 + app/src/settings_view/agent_providers_widget.rs | 2 +- app/src/settings_view/ai_page.rs | 10 ++++++++-- 5 files changed, 12 insertions(+), 3 deletions(-) diff --git a/app/i18n/en/warp.ftl b/app/i18n/en/warp.ftl index ef4741c7ae..186bc5e36b 100644 --- a/app/i18n/en/warp.ftl +++ b/app/i18n/en/warp.ftl @@ -1516,6 +1516,7 @@ settings-agent-providers-quick-add-title = Quick add settings-agent-providers-refresh-catalog = Refresh catalog settings-agent-providers-loading-catalog = Loading models.dev catalog… (the first load may take a few seconds) settings-agent-providers-catalog-empty = models.dev catalog is empty. Click [Refresh catalog] to retry. +settings-agent-providers-catalog-load-failed = Failed to load models.dev catalog. Click [Refresh catalog] to retry. settings-agent-providers-no-match = No match for "{ $query }" settings-agent-providers-collapse = Collapse ▲ settings-agent-providers-expand-remaining = Expand remaining { $count } ▼ diff --git a/app/i18n/ja/warp.ftl b/app/i18n/ja/warp.ftl index fc27cd05d5..d9d64601de 100644 --- a/app/i18n/ja/warp.ftl +++ b/app/i18n/ja/warp.ftl @@ -1451,6 +1451,7 @@ settings-agent-providers-quick-add-title = クイック追加 settings-agent-providers-refresh-catalog = カタログを更新 settings-agent-providers-loading-catalog = models.dev カタログを読み込み中… (初回読み込みは数秒かかる場合があります) settings-agent-providers-catalog-empty = models.dev カタログが空です。[カタログを更新] をクリックして再試行してください。 +settings-agent-providers-catalog-load-failed = models.dev カタログの取得に失敗しました。[カタログを更新] をクリックして再試行してください。 settings-agent-providers-no-match = 「{ $query }」に一致する項目はありません settings-agent-providers-collapse = 折りたたむ ▲ settings-agent-providers-expand-remaining = 残り { $count } 件を展開 ▼ diff --git a/app/i18n/zh-CN/warp.ftl b/app/i18n/zh-CN/warp.ftl index 06e9fdcaf6..faf0024072 100644 --- a/app/i18n/zh-CN/warp.ftl +++ b/app/i18n/zh-CN/warp.ftl @@ -1491,6 +1491,7 @@ settings-agent-providers-quick-add-title = 快速添加 settings-agent-providers-refresh-catalog = 刷新目录 settings-agent-providers-loading-catalog = 正在拉取 models.dev 目录…(第一次可能需要几秒) settings-agent-providers-catalog-empty = models.dev 目录为空,点 [刷新目录] 重试。 +settings-agent-providers-catalog-load-failed = models.dev 目录拉取失败,点 [刷新目录] 重试。 settings-agent-providers-no-match = 无匹配「{ $query }」 settings-agent-providers-collapse = 收起 ▲ settings-agent-providers-expand-remaining = 展开剩余 { $count } 个 ▼ diff --git a/app/src/settings_view/agent_providers_widget.rs b/app/src/settings_view/agent_providers_widget.rs index 6e989c7614..23c0d14193 100644 --- a/app/src/settings_view/agent_providers_widget.rs +++ b/app/src/settings_view/agent_providers_widget.rs @@ -1463,7 +1463,7 @@ impl AgentProvidersWidget { match models_dev::cached() { None => { let catalog_text = if models_dev::last_fetch_failed() { - crate::t!("settings-agent-providers-catalog-empty") + crate::t!("settings-agent-providers-catalog-load-failed") } else { crate::t!("settings-agent-providers-loading-catalog") }; diff --git a/app/src/settings_view/ai_page.rs b/app/src/settings_view/ai_page.rs index 44626805ba..0d3b896514 100644 --- a/app/src/settings_view/ai_page.rs +++ b/app/src/settings_view/ai_page.rs @@ -3401,7 +3401,10 @@ impl TypedActionView for AISettingsPageView { ctx.spawn( async move { models_dev::fetch_and_cache(client).await }, |view, result, ctx| match result { - Ok(()) => view.rebuild_current_page(ctx), + Ok(()) => { + models_dev::set_fetch_failed(false); + view.rebuild_current_page(ctx); + } Err(e) => { log::warn!("[models.dev] 拉取失败: {e}"); models_dev::set_fetch_failed(true); @@ -3419,7 +3422,10 @@ impl TypedActionView for AISettingsPageView { ctx.spawn( async move { models_dev::fetch_and_cache(client).await }, |view, result, ctx| match result { - Ok(()) => view.rebuild_current_page(ctx), + Ok(()) => { + models_dev::set_fetch_failed(false); + view.rebuild_current_page(ctx); + } Err(e) => { log::warn!("[models.dev] 刷新失败: {e}"); models_dev::set_fetch_failed(true);