Skip to content

fix: models.dev 拉取失败后 UI 永久卡在 loading 态 (#145)#285

Open
zerx-lab wants to merge 2 commits into
mainfrom
claude/sleepy-meitner-5w7pjv
Open

fix: models.dev 拉取失败后 UI 永久卡在 loading 态 (#145)#285
zerx-lab wants to merge 2 commits into
mainfrom
claude/sleepy-meitner-5w7pjv

Conversation

@zerx-lab

Copy link
Copy Markdown
Owner

关联 Issue

Fixes #145

问题点(确定性描述)

根因app/src/settings_view/ai_page.rs:3403–3406

|view, result, ctx| match result {
    Ok(()) => view.rebuild_current_page(ctx),
    Err(e) => log::warn!("[models.dev] 拉取失败: {e}"),  // ← bug
},

EnsureModelsDevLoaded(及 RefreshModelsDev)的 Err 分支仅执行 log::warn!,既未调用 ctx.notify() 触发 UI 重绘,也未更新任何可观测状态。结果:

  • models_dev::cached()models_dev.rs:135)始终返回 None
  • agent_providers_widget.rs:1463match models_dev::cached()None 分支永远渲染 settings-agent-providers-loading-catalog("正在拉取…"占位)
  • 用户无法感知失败,页面永久卡在 loading 状态,即使点击 Refresh 按钮也同样卡死

复现证据

证据 1:EnsureModelsDevLoaded Err 分支无 notify
  文件:app/src/settings_view/ai_page.rs:3403-3406
  Err(e) => log::warn!("[models.dev] 拉取失败: {e}"),
  结论:✅ 与 issue 描述一致

证据 2:cached() 仅读 State.catalog,失败路径不写入
  文件:app/src/ai/agent_providers/models_dev.rs:135-137
  pub fn cached() -> Option<Catalog> {
      state().read().ok().and_then(|s| s.catalog.clone())
  }
  结论:✅ 失败后 catalog 仍为 None

证据 3:None 分支无差异渲染
  文件:app/src/settings_view/agent_providers_widget.rs:1463-1477
  None => { /* 永远显示 loading 文字 */ }
  结论:✅ 与 issue 描述一致

规模声明

指标
修改文件数 3(≤ 3 ✅)
新增/修改行数 +30 / -3(≤ 50 ✅)
cached() 调用方影响面 3 处,语义无需变更 ✅
新增依赖 无 ✅
公共 API / schema 变更 无 ✅

修改文件清单

文件 行号范围 改动说明
app/src/ai/agent_providers/models_dev.rs 272–284 新增 FETCH_FAILED: AtomicBool + last_fetch_failed() + set_fetch_failed()
app/src/settings_view/ai_page.rs 3405–3409, 3423–3427 EnsureModelsDevLoadedRefreshModelsDev 的 Err 分支改为 set_fetch_failed(true) + ctx.notify()
app/src/settings_view/agent_providers_widget.rs 1464–1469 None 分支检查 last_fetch_failed(),失败时显示含重试提示的文字而非 loading 文字

验证

网络层面 cargo check 因远程环境无法拉取 git 依赖而失败(proxy 403)。
已通过 rustc 单独验证新增 AtomicBool 代码语法正确,逻辑改动均为对现有模式的最小扩展(与 CHIPS_EXPANDED 完全同构)。

影响面

  • models_dev::cached() 的 3 处调用方:agent_providers_widget.rs:1463(本 PR 修改)、ai_page.rs:3427ai_page.rs:3458(均为 let Some(...) else { return } 模式,语义不变)
  • 页首 Refresh 按钮(agent_providers_widget.rs:1437-1442)始终渲染,失败后用户可直接点击重试

🤖 Generated with Claude Code

https://claude.ai/code/session_017po385dpt8y1iuhpDixXbT


Generated by Claude Code

根因: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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017po385dpt8y1iuhpDixXbT

@zerx-lab zerx-lab left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(详见 inline comments 和下方总结 comment)

— 由 Claude Routine 自动生成;如需复评请 @ 维护者人工触发。


Generated by Claude Code

Comment thread app/src/settings_view/ai_page.rs Outdated
@@ -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),

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

成功路径没有调用 models_dev::set_fetch_failed(false)。PR body 的说明是"成功时 cached()Some,无需重置"——正常流程下确实如此,但这个推断依赖一个隐式前提:catalog 一旦写入就不会被清空。若将来 catalog 因某种原因(内存回收、版本升级清缓存、State 重建等)被重置为 NoneFETCH_FAILED 会停留在 true,下次进入 None 分支时会错误地显示失败态,即便实际上最近一次拉取已经成功。

建议在 Ok(()) 分支也加一行 models_dev::set_fetch_failed(false);,让标志严格反映最近一次拉取的实际结果,两个 handler(EnsureModelsDevLoadedRefreshModelsDev)都需要同步修改。


Generated by Claude Code


match models_dev::cached() {
None => {
let catalog_text = if models_dev::last_fetch_failed() {

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question:这里复用了 settings-agent-providers-catalog-empty,但这个 key 的语义是"目录为空",而此处的上下文是"网络拉取失败"。两种情形对用户的含义截然不同——前者暗示配置问题或 providers 未接入,后者是网络/CDN 故障。Issue #145 中明确要求渲染独立的错误态("设置后应该显示错误文字而不是 loading 文字")。

建议新增一条专用 i18n key(例如 settings-agent-providers-catalog-load-failed),措辞应说明拉取失败并引导用户点击页面顶部已存在的 Refresh 按钮重试,避免与"空目录"产生歧义。


Generated by Claude Code

Copy link
Copy Markdown
Owner Author

Review 总结

整体判断:建议修改后合并(Request Changes 倾向,但因同账号限制以 Comment 形式提交)

两处问题需要处理后才宜合并,逻辑方向是正确的,改动量小。


阻塞问题

1. FETCH_FAILED 在成功路径未重置ai_page.rs

EnsureModelsDevLoadedRefreshModelsDev 两个 handler 的 Ok(()) 分支均未调用 models_dev::set_fetch_failed(false)。PR body 的说明"成功时 cached()Some,无需重置"在当前实现里基本成立,但依赖一个隐式前提——catalog 一旦写入就永久有效。若将来 State 因任何原因被重置为 None(内存压力、版本迁移、测试重置等),FETCH_FAILED 会以 true 僵死,UI 永远显示失败提示。建议在两处 Ok(()) 分支各加一行 models_dev::set_fetch_failed(false);

2. 复用了语义不符的 i18n keyagent_providers_widget.rs

settings-agent-providers-catalog-empty(含义:"目录为空")来代替"拉取失败"提示,对用户而言容易误判为配置问题而非网络问题。Issue #145 明确要求独立的错误态文字。建议新增专用 key,例如 settings-agent-providers-catalog-load-failed,并在三个 i18n 文件(en/warp.ftlzh-CN/warp.ftlja/warp.ftl)中同步添加,措辞引导用户点击 Refresh 按钮重试。


建议(不阻塞)


看起来不错的地方

  • 复用 CHIPS_EXPANDEDAtomicBool 模式,保持了代码内部一致性。
  • 两个 action handler 对称修改,没有遗漏 RefreshModelsDev
  • ctx.notify() 调用位置正确,触发时机与其他类似 Err 路径一致。

— 由 Claude Routine 自动生成;如需复评请 @ 维护者人工触发。


Generated by Claude Code

- 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017po385dpt8y1iuhpDixXbT
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AI 设置页: models.dev 首次拉取失败后 UI 永久卡在 loading 态

2 participants