t191/t192:daemon 主視窗(Google Drive 風格)+「同步中…」進行中狀態

解 issue #18+#17(leo 指定併做)。

## #17 進行中狀態——真兇是「工作時間對用戶隱形」
leo:「按『立刻同步』後很快就回到『看守中』,看起來好像就做完了,
      這時使用者一看沒做完啊,就覺得是 bug」。
真兇:collector **只在一輪跑完後**才印 JSON ⇒ 托盤無從得知「正在跑」。
修:開工前先印 phase:"start"、跑完印 phase:"done";
    supervisor 新增 StateSyncing;托盤/主視窗顯示「同步中… 正在讀檔並整理成知識卡」。
形狀相容:兩筆都有 at,舊版托盤只會多算一次 round,不會壞。

實測(真的跑一輪 --once):
  第1筆 phase='start' at=21:31:26
  第2筆 phase='done'  at=21:31:27

## #18 主視窗(新檔 mainwindow.go)
leo:「daemon 設定項越來越多,不可能統統塞在下拉選單」
     「參考 Google Drive 設定畫面:點擊 daemon 就開一個視窗,佔螢幕約 1/2」
     「資料夾清單要可捲動——我每個 gitea 專案都要同步,那就是幾十個,根本塞不下」
- 920x620 視窗,關窗=隱藏(daemon 續跑)
- 上:大字狀態+副標(上次同步/已整理幾份/失敗幾份;有錯誤優先顯示)
- 中:widget.List 虛擬捲動的資料夾清單(帳號標題+其下資料夾攤平成單層)
- 下:白話動作列(加入資料夾/新增知識庫帳號/AI 設定/檢查更新)
- 每秒自動刷新 ⇒ 同步中看得到在動
- 無帳號時引導去連線(onboarding),不是丟錯誤或空面板
- **所有動作複用既有 handler**,不在視窗那邊重寫一份邏輯

托盤選單第一項加「開啟 Arcrun…」——leo 已點破兩次
「能力做出來了,入口沒出現在用戶會看的地方」(docs 沒連結/MCP 零處提及),
這次做完就讓它看得見。

測試:t192_test.go 八則(狀態文案四態/同步中要說明在做什麼/
副標優先顯示錯誤/副標不可空白/清單攤平且 accIdx 正確/60 個資料夾/無帳號空清單)全過。
collector 與 supervisor 全套綠。

⚠️ 未驗:fyne GUI 的實際外觀需真機開窗(無螢幕環境驗不到),
   CIS 視覺套用待 leo 看過畫面再調。
This commit is contained in:
2026-08-04 21:32:31 +08:00
parent 8c7b50d9a9
commit cc6534dd39
5 changed files with 500 additions and 2 deletions
+72
View File
@@ -1009,6 +1009,65 @@ func main() {
}, win)
}
// 🔴 t192leo 08-04issue #18):主視窗——「daemon 設定項越來越多,
// **不可能統統塞在下拉選單**」「參考 Google Drive 設定畫面:點擊 daemon
// 就開一個視窗,佔螢幕約 1/2」「資料夾清單要**可捲動**」。
// 托盤選單保留(快速動作),但**主入口改成這個視窗**。
// 所有動作都**複用既有 handler**,不在視窗那邊重寫一份邏輯。
mw := newMainWindow(a)
mw.sup = sup
mw.cfg = cfg
mw.reload = func() *directConfig { return cfg }
mw.onAddAccount = func() { showConnectWizard() }
mw.onAddFolder = func(accIdx int) {
dialog.ShowFolderOpen(func(uri fyne.ListableURI, err error) {
if err != nil || uri == nil {
return
}
addAccountWatchFolder(cfg, accIdx, uri.Path())
if serr := saveConfig(cfg); serr != nil {
dialog.ShowError(serr, mw.win)
return
}
restartWatch()
rebuildTray()
mw.refresh()
}, mw.win)
}
mw.onDelFolder = func(accIdx int, folder string) {
removeAccountWatchFolder(cfg, accIdx, folder)
if serr := saveConfig(cfg); serr != nil {
dialog.ShowError(serr, mw.win)
return
}
restartWatch()
rebuildTray()
mw.refresh()
}
mw.onAISettings = func() { showAISettings() }
mw.onSyncNow = func() {
_ = os.MkdirAll(appDir(), 0o755)
_ = os.WriteFile(syncNowSignalPath(), []byte{}, 0o644)
mw.refresh()
}
mw.onCheckUpdate = func() {
go func() {
st := checkForUpdate()
switch {
case st.Err != "":
dialog.ShowInformation("檢查更新", "暫時連不上更新伺服器:\n"+st.Err, mw.win)
case st.Available:
go prepareUpdateInBackground()
dialog.ShowInformation("有新版本",
"發現新版 "+st.Latest+",正在背景下載。\n下載完成後會出現「重新啟動完成更新」。", mw.win)
default:
dialog.ShowInformation("檢查更新", "你已經是最新版本("+versionLabel()+")。", mw.win)
}
}()
}
mw.build()
mw.startAutoRefresh()
// t98:立刻同步——寫訊號檔觸發 collector 立即跑一輪,無需等下一個定時週期。
// syncNowItem 宣告在 rebuildTray 外,確保 click handler 的 goroutine 能還原標籤後重建選單。
var syncNowItem *fyne.MenuItem
@@ -1078,6 +1137,14 @@ func main() {
var items []*fyne.MenuItem
// 🔴 t192:主視窗入口放**選單第一項**——leo 點破過同一個病兩次:
// 「能力做出來了,入口沒出現在用戶會看的地方」
// (docs 建好卻沒連結/MCP 端點活著卻零處提及)。做完就要看得見。
items = append(items,
fyne.NewMenuItem("開啟 Arcrun…", func() { mw.showAndRefresh() }),
fyne.NewMenuItemSeparator(),
)
// t104per-account 分組——每個帳號獨立一段(帳號名稱標題 + 資料夾 + 新增按鈕)。
if len(cfg.Accounts) > 0 {
for ai, acc := range cfg.Accounts {
@@ -1259,6 +1326,11 @@ func buildStatusLabel(s supervisor.Status, sync traySyncStatus, extractor string
base = fmt.Sprintf("看守中 · 上次同步 %s", s.LastRoundAt.Local().Format("15:04"))
}
return base + syncStatusLabel(sync, hasExtractor)
// 🔴 t191leo 08-04issue #17):正在跑就要**看得出來在跑**。
// leo:「如果它顯示『萃取中』、『同步中』⋯⋯而不是看起來好像就做完了,
// 這時使用者一看沒做完啊,就覺得是 bug」「這是不讓用戶誤解的方法」。
case supervisor.StateSyncing:
return "同步中… 正在讀檔並整理成知識卡"
case supervisor.StateStarting:
return "啟動中…"
case supervisor.StateError: