// sync_status.go — 每輪同步後的彙總狀態(t91 狀態可見性)。 // 寫成 JSON 供托盤讀取,讓使用者第一眼看到萃取是否正常。 package main import ( "encoding/json" "os" "path/filepath" ) // AccountSyncStatus 彙總單一帳號的每輪同步結果(t104 多帳號看守)。 // key in SyncStatus.AccountDetails = instanceHostOf(cypher_url)。 type AccountSyncStatus struct { LastSync string `json:"last_sync,omitempty"` CloudVersion string `json:"cloud_version,omitempty"` // t103 per-account CloudCheckOK bool `json:"cloud_check_ok"` ExtractedOK int `json:"extracted_ok"` ExtractFailed int `json:"extract_failed"` // t182(leo 08-04:「沒裝好就顯示 workers AI 還沒通,一旦通了就顯示可用」): // 這個帳號的雲端實例有沒有 /portal/daemon/extract。**逐帳號**各自記—— // 用戶可能有多個實例、更新進度不同步。只在走 workers-ai 這條路時探測。 CloudAIReady bool `json:"cloud_ai_ready"` CloudAINote string `json:"cloud_ai_note,omitempty"` // 還沒通時的白話說明(含該做什麼) } // SyncStatus 彙總每輪同步的萃取結果,持久化至 ~/.arcrun-rag/status.json。 // 托盤依此決定顯示「已萃 N 檔」、「⚠ 萃取失敗 M 檔」還是「⚠ 萃取引擎未就緒」。 type SyncStatus struct { LastSync string `json:"last_sync,omitempty"` // RFC3339,最近一輪完成時間 ExtractedOK int `json:"extracted_ok"` // 本輪萃取成功件數(跨帳號累計) ExtractFailed int `json:"extract_failed"` // 本輪萃取失敗件數(跨帳號) Failures []ExtractFail `json:"failures,omitempty"` // 失敗清單(路徑+白話原因) ExtractorOK bool `json:"extractor_ok"` // 萃取器本身是否就緒(預檢,機器層級) ExtractorError string `json:"extractor_error,omitempty"` // 未就緒的白話原因 // 頂層 cloud 欄位保留向後相容(單帳號時同時填頂層+AccountDetails) CloudVersion string `json:"cloud_version,omitempty"` // bundle_version(單帳號時填) CloudCheckOK bool `json:"cloud_check_ok"` // /health 可達才為 true // t104:per-account 狀態(key = instanceHostOf(cypher_url)) AccountDetails map[string]AccountSyncStatus `json:"account_details,omitempty"` } // ExtractFail 記一筆萃取失敗(路徑+白話原因)。 type ExtractFail struct { Path string `json:"path"` Error string `json:"error"` } // StatusFilePath 回傳狀態檔路徑:與 manifest 同目錄的 status.json。 func StatusFilePath(manifestPath string) string { return filepath.Join(filepath.Dir(manifestPath), "status.json") } // SaveSyncStatus 寫入(覆蓋)狀態檔。失敗只印 stderr,不擋看守本體。 func SaveSyncStatus(path string, s SyncStatus) error { if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { return err } data, _ := json.MarshalIndent(s, "", " ") return os.WriteFile(path, data, 0o644) } // LoadSyncStatus 讀取狀態檔;不存在或解析失敗回零值+error(托盤自行降級)。 func LoadSyncStatus(path string) (SyncStatus, error) { var s SyncStatus data, err := os.ReadFile(path) if err != nil { return s, err } err = json.Unmarshal(data, &s) return s, err } // SyncNowSignalPath 回傳立刻同步訊號檔路徑:與 manifest 同目錄的 sync-now。 // tray 寫入此檔 → collector 偵測到後立刻跑一輪同步並刪除它。 func SyncNowSignalPath(manifestPath string) string { return filepath.Join(filepath.Dir(manifestPath), "sync-now") }