feat(t215): 每個知識庫顯示是否要更新,落後就給 install 頁連結

leo 08-08:「在每個知識庫會看到其他需要更新的,在每個知識庫上顯示是否要更新,
如果要,加開啓 install 頁的連結」。小幫手以前只提示自己(daemon 本體)要不要
更新,使用者連著多個雲端知識庫時完全看不出哪一個落後。

- collector/cloud_latest.go:EvalCloudUpdate + FetchLatestCloudRelease(30 分鐘節流),
  判準與 portal 版本卡 loadVersion() 同一套(bundle_version vs install.arcrun.dev/
  api/latest 的 release,semver 逐段整數比較),不是 t103 minCloudRelease 那把相容
  底線,避免同一個知識庫在兩處得到相反答案。
- direct.go/sync_status.go:每輪同步順帶算好每個帳號的 CloudUpdateKnown/
  CloudUpdateStale/CloudLatest,寫進 status.json。
- app.go:GetState 把這些欄位接進 UIAccount,供前端讀。
- main.js/style.css:首頁新增「知識庫版本」卡(每庫一行,落後才出現「前往安裝頁
  更新」按鈕,帶 email 預填);側邊欄庫名旁加警示點;各庫頁也顯示同一行版本狀態。
  查不到版本(連不上/latest 暫時查不到)一律誠實說「查不到」,不當成「已最新」。

已用假 window.go 在瀏覽器實測四種情境(落後/已最新/連不上/查得到 mine 但查不到
latest)+零知識庫的 onboarding 頁+深色模式,畫面與按鈕行為皆符合預期。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-09 00:21:40 +08:00
parent 2bbfa453d3
commit 779a1b801e
8 changed files with 328 additions and 10 deletions
+24 -2
View File
@@ -96,6 +96,11 @@ type syncStatus struct {
// 不重新定義結構,避免兩邊的欄位定義漂移。
Progress collector.SyncProgress `json:"progress"`
FailureBreakdown collector.FailureBreakdown `json:"failure_breakdown"`
// t2152026-08-08):per-account 雲端版本狀態——collector.AccountSyncStatus 已經是
// GetState 要的形狀(含 t215 新欄位 CloudUpdateKnown/CloudUpdateStale/CloudLatest),
// 直接原樣接住,不重新定義一份會漂移的結構。key = instanceHostOf(cypher_url)
// (與 UIAccount.Host 同一套算法,見 shortHost)。
AccountDetails map[string]collector.AccountSyncStatus `json:"account_details,omitempty"`
}
type skippedDoc struct {
@@ -219,6 +224,15 @@ type UIAccount struct {
Name string `json:"name"`
Host string `json:"host"`
Folders []UIFolder `json:"folders"`
// t2152026-08-08leo:「在每個知識庫上顯示是否要更新,如果要,加開啓 install 頁的
// 連結」)——一個使用者可能連著不只一個知識庫,之前只有小幫手自己的版本會提示更新,
// 每個知識庫各自的雲端版本完全沒有畫面。判準與 portal 版本卡同一套
// collector.EvalCloudUpdate,不是 t103 的相容底線),這裡只翻成人話,不重新判斷。
CloudVerKnown bool `json:"cloudVerKnown"` // false=查不到(連不上/還沒查過),前端要老實說「查不到」
CloudVerStale bool `json:"cloudVerStale"` // true=有新版可更新
CloudVerMine string `json:"cloudVerMine,omitempty"` // 這個知識庫目前的版本(可能連 Known=false 時也有值)
CloudVerLatest string `json:"cloudVerLatest,omitempty"` // 已知的最新版
Email string `json:"email,omitempty"` // 供「前往安裝頁更新」預填帳號(同 portal 版本卡的做法)
}
type UIState struct {
Version string `json:"version"`
@@ -415,11 +429,20 @@ func (a *App) GetState() UIState {
}
}
sync := loadSyncStatus()
for i, acc := range cfg.Accounts {
ui := UIAccount{Name: accountName(acc), Host: shortHost(acc.CypherURL)}
ui := UIAccount{Name: accountName(acc), Host: shortHost(acc.CypherURL), Email: acc.Email}
for _, f := range acc.WatchFolders {
ui.Folders = append(ui.Folders, UIFolder{Path: f, AccIdx: i})
}
// t215per-account 雲端版本狀態——key 與 Host 同一套算法(shortHost),
// 對應 collector 寫入 status.json 時用的 instanceHostOf(兩者對一般 https URL 同值)。
if accSt, ok := sync.AccountDetails[ui.Host]; ok {
ui.CloudVerKnown = accSt.CloudUpdateKnown
ui.CloudVerStale = accSt.CloudUpdateStale
ui.CloudVerMine = accSt.CloudVersion
ui.CloudVerLatest = accSt.CloudLatest
}
st.Accounts = append(st.Accounts, ui)
}
@@ -431,7 +454,6 @@ func (a *App) GetState() UIState {
st.GeminiKey = "••••••••"
}
sync := loadSyncStatus()
st.Syncing, st.StatusBig, st.StatusSub = describeStatus(sync)
st.Steps = buildSteps(sync, st.Syncing)
st.Skipped = buildSkipped(sync)