Files
arcrun-collector/sync_status.go
T
Leo 133f5add46 t182:老 config 抹除成 Workers AI+雲端探測——leo 實測「無卡」的真因
leo 08-04 實測回報(更新 v0.15.5 後仍顯示 Gemini/丟 PDF 無卡),
查出來不是 Workers AI 不通,是**根本沒走到那條路**。三顆真 bug:

① 抹除只做一半(leo:「就要抹除改成用 Workers AI,如果保持 Gemini
   它不會改掉,那就是失敗的」)
   t181 只在 RunDirectOnce 改頂層記憶體值,但
   - makeAccountSubConfig 會把**帳號層**舊值蓋回來(t126「帳號層優先」)
   - 完全**沒寫回檔案** ⇒ 托盤是另一個行程,讀檔還是念 Gemini
   leo 的 config 正是頂層+兩個帳號各留 "gemma"、無 explicit
   ⇒ 畫面顯示 Gemini、萃取也真的跑 Gemini。
   修:遷移移到 LoadDirectConfig(每次啟動必經),逐層抹除 + 寫回檔案。
   金鑰保留(Gemini 是選配不是廢除)。

② 驗證器擋掉自己的新預設
   `extractor 只能是 claude / gemma` ⇒ config 一旦寫成 workers-ai,
   daemon 直接載入失敗起不來。v0.15.5 已帶著這顆出貨。

③ 托盤標籤照 config 舊字串念(t178 的通則版)
   t178 只把 claude 這**一個**殘留值導向 Gemini,殘留 gemma 一樣脫鉤。
   改成與 direct.go 同一條判準:沒 explicit 就一律念「雲端 AI」。

+ t182 雲端探測(leo 指定設計:「會去掃一次看雲端是否裝好,沒裝好就顯示
  workers AI 還沒通,一旦通了就顯示可用」):ProbeWorkersAI 逐帳號探
  /portal/daemon/extract(送空 text,不燒 LLM 額度),結果寫進 status.json
  由托盤「狀態:」講白話。**不做靜默退回 Gemini**——那會讓用戶永遠
  不知道自己雲端沒更新。

實測證據(非 mock,打真實例):
- youlin 實例萃真卡:3.67s,產出完整知識卡(一句話定義/要點/關鍵實體/關聯)
- 連測三次:3.51s / 3.34s / 3.59s(Gemini 實測 16.87s ⇒ 快約 5 倍)
- 兩個實例都已有 /portal/daemon/extract(皆回 400=route 存在)
- 拿 leo 真 config 複本跑遷移:三層全抹成 workers-ai、金鑰留著、重讀仍是 workers-ai

測試:新增 direct_t182_test.go(抹除全層/寫回磁碟/主動選過不動/合法值/冪等);
既有三則因預設變更而失效的斷言已更新(t108 二進位不出機的契約未放寬,仍綠)。
全套綠。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 16:29:56 +08:00

78 lines
3.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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"`
// t182leo 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
// t104per-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")
}