fix(arcrun-rag#59): 額度卡與雲端連線兩則假訊息改口

leo21c 畫面同時說「今天已經幫你整理了 0 份」與「額度用完,可以換一個模型,
或升級 Cloudflare」——一份都沒成功,額度其實是被同帳號正在做的向量化
(嵌入固定走 Workers AI,與萃取共用同一份每日免費額度)吃光的,換萃取模型
救不了。改法:buildQuotaNotice 在 dailyCount==0(本輪一份都沒吃到額度就先
撞牆)時換一套出口/保證句——不再建議換模型、不再無條件承諾「明天會自動
接著跑」,只留「升級 Cloudflare」這個結構上真的有效的出口;dailyCount>0
(單純量大用完)維持原三句話不動。

同批修另一則真因已被頂層 wiki 鎖定的假訊息:leo 三個帳號 /health 全部 200,
畫面卻顯示「沒連上雲端」——direct.go 寫 status.json 頂層 cloud_check_ok 時
以前只有剛好一個帳號才會填,2+ 帳號(leo 的常態)時恆為 Go 零值 false。
改成不論帳號數,任一帳號連得上就標頂層為 true。

兩者都有回歸測試:對修改前的程式碼跑會 FAIL(多帳號 CloudCheckOK 案例已
用 git stash 實測驗證),修完後 PASS。額度卡文案另外用真正的 frontend/dist
+ 假 window.go 灌 leo21c 現場資料,Claude Browser 實際開起來看過,確認新
文案正確渲染、不再出現「可以換一個模型」、console 無紅字。

未做:兩者都只在原始碼層修好,尚未重打 daemon bundle 出貨(0.18.25 用戶
手上還看不到);頂層 cloud_check_ok 的實際消費端(除已知的舊版 tray)未
完全追出,留給下一輪核實。詳見 system-dev/docs/3-specs/daemon-beta/tasks.md
t216/t217。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-11 12:28:49 +08:00
parent 3862a1a877
commit 4cbc7ebe11
6 changed files with 209 additions and 18 deletions
+38 -6
View File
@@ -61,25 +61,57 @@ func (n QuotaNotice) Combined() string {
// buildQuotaNotice 組出三句話。dailyCount=今天(UTC 日界,與額度重置同一條線)已成功
// 萃取的份數;resetAtnextQuotaResetTaiwan 算出的下一次重置時間。
//
// 🔴 arcrun-rag#592026-08-10 leo 實查):`dailyCount==0` 時原本這三句話會自相矛盾——
// 「今天已經幫你整理了 0 份」搭「可以換一個模型」,一份都沒成功,額度是誰用掉的?
// leo 查出真兇:**嵌入(向量化)與萃取共用同一份 Workers AI 每日免費額度**
// `matrix/arcrun/kbdb/src/embed.ts` 的 `DEFAULT_EMBED_MODEL` 固定走 Workers AI
// 不受萃取模型選擇影響)。dailyCount==0 且已進冷卻,代表這一輪萃取連一份都沒吃到
// 額度就先被別的事(最典型是帳號上同時在做的大量重新嵌入)用光了——
// 這個情況下「換一個模型」是結構上做不到的假出口(換的是萃取模型,嵌入不會跟著換),
// 「明天會自動接著跑」也是做不到的承諾(只要那件事還在燒同一份額度,明天還是會撞同一面牆)。
// dailyCount>0(今天多少有做出東西,只是單純量大用完)維持原本三句話不動——
// 那種情境三句話依然成立,換模型也確實能減少萃取那一半吃掉的份額。
//
// 🔴 Achievement 句的格式刻意不因分支而變:`ClassifyFailure`progress.go)靠
// 「幫你整理了」這個子字串把這段三句話歸進 FailQuotaExhausted 分類,兩個分支都要留著。
func buildQuotaNotice(now time.Time, dailyCount int, resetAt time.Time) QuotaNotice {
achievement := fmt.Sprintf("今天已經幫你整理了 %d 份", dailyCount)
if dailyCount == 0 {
return QuotaNotice{
Achievement: achievement,
ExitOptions: "換一個模型救不了這個:你的雲端知識庫本身也在用同一份免費額度做別的事" +
"(例如重新整理索引),額度是在那邊被用光的,不是被這次的整理用掉的。" +
"真正能解除限制的只有升級 Cloudflare(每月 5 美元,移除每日免費上限)",
Guarantee: fmt.Sprintf(
"額度會在%s早上 8:00 重置,但如果同一件事還在佔用額度,你可能會再撞到同一面牆——"+
"不是保證接下來就會一路處理完",
quotaResetDayWord(now, resetAt)),
ResumeAt: resetAt.Format(time.RFC3339),
}
}
return QuotaNotice{
Achievement: fmt.Sprintf("今天已經幫你整理了 %d 份", dailyCount),
Achievement: achievement,
ExitOptions: "可以換一個模型,或升級 Cloudflare(每月 5 美元)",
Guarantee: quotaGuaranteeText(now, resetAt),
ResumeAt: resetAt.Format(time.RFC3339),
}
}
// quotaGuaranteeText 把重置時間換成人話:「今天」或「明天」早上 8:00
// quotaResetDayWord 把重置時間換成「今天」或「明天」
// (不能寫死「明天」——若這一刻台灣時間已經過了午夜、還沒到 8 點,重置其實是「今天」)。
func quotaGuaranteeText(now, resetAt time.Time) string {
func quotaResetDayWord(now, resetAt time.Time) string {
nowTW := now.In(taiwanTZ)
resetTW := resetAt.In(taiwanTZ)
dayWord := "明天"
if nowTW.Year() == resetTW.Year() && nowTW.YearDay() == resetTW.YearDay() {
dayWord = "今天"
return "今天"
}
return fmt.Sprintf("不花錢也沒關係,%s早上 8:00 會自動恢復、會接著跑", dayWord)
return "明天"
}
// quotaGuaranteeText 把重置時間換成人話:「今天」或「明天」早上 8:00。
func quotaGuaranteeText(now, resetAt time.Time) string {
return fmt.Sprintf("不花錢也沒關係,%s早上 8:00 會自動恢復、會接著跑", quotaResetDayWord(now, resetAt))
}
// quotaState 是「這個帳號本輪的額度冷卻」共享狀態,跨同一帳號的多個監看根