v0.18.19:失敗訊息改成人話(leo:「這個錯誤訊息我真的看不懂」)

v0.18.18 雖然把失敗檔案列出來了,但寫的是
「上次失敗(第 5 次),5h38m38s 後重試(上面是自動重試的排程,真正的原因是前一次失敗)」
——**那是我寫給自己看的**:只是把 collector 的原文轉貼、再補一句解釋為什麼看起來怪。

使用者要的是三件事:**現在怎樣、為什麼、我要不要做什麼**。

## 改法
- 解析退避訊息取出「試幾次/多久後再試/原因」,重新組成人話
- **有原因**時以原因為主、排程放後面補充(他要判斷的是原因)
- **沒有原因**時(舊版失敗的檔案沒存 LastError)誠實說:
  「已經自動試過 5 次都沒成功,約 5 小時後會再試一次。這個檔是在舊版失敗的,
    當時沒有記下原因;如果一直沒好,把紀錄檔傳給我們。」
- 時間不再精確到秒(`5h38m38s` → 「約 5 小時」)——使用者要的是量級不是秒數

## 實測輸出(三種情境都印出來看過)
  舊:上次失敗(第 5 次),5h38m38s 後重試(上面是自動重試的排程…)
  新:已經自動試過 5 次都沒成功,約 5 小時後會再試一次。這個檔是在舊版失敗的…
  新:今天的免費 AI 額度用完了 ⇒ 明天會自動恢復,這些檔案會自己補上,你不用做什麼。
  新:這份 PDF 看起來是掃描的圖片 ⇒ 需要先做文字辨識(OCR),或換一份有文字的版本。
This commit is contained in:
2026-08-06 22:02:32 +08:00
parent 857420dfa7
commit d0852e9b45
2 changed files with 53 additions and 3 deletions
+2 -1
View File
@@ -1,5 +1,6 @@
{
"_algo": 2,
"v0.18.17": "cb6e53a052b87482",
"v0.18.18": "a495451d9d9da78a"
"v0.18.18": "a495451d9d9da78a",
"v0.18.19": "b754d7ff97396794"
}
+51 -2
View File
@@ -23,8 +23,10 @@ import (
"os"
"os/exec"
"path/filepath"
"regexp"
runtime2 "runtime"
"sort"
"strconv"
"strings"
"time"
@@ -278,6 +280,39 @@ type UIFailItem struct {
// maxFailShown=最多列幾份(多了會洗版;總數仍照實講)。
const maxFailShown = 8
// parseRetryMessage 從退避訊息裡取出「試了幾次、多久後再試、原因是什麼」。
// 格式來源=collector 的 retrySkipReason()。解析失敗就回 ok=false,由呼叫端原樣顯示。
func parseRetryMessage(raw string) (n int, wait, reason string, ok bool) {
m := retryMsgRe.FindStringSubmatch(raw)
if m == nil {
return 0, "", "", false
}
n, _ = strconv.Atoi(m[1])
wait = humanizeDuration(m[2])
if i := strings.Index(raw, "|原因:"); i >= 0 {
reason = raw[i+len("|原因:"):]
}
return n, wait, reason, true
}
var retryMsgRe = regexp.MustCompile(`上次失敗(第 (\d+) 次),([0-9hms.]+) 後重試`)
// humanizeDuration 把 `5h38m38s` 講成「5 小時」——使用者不需要精確到秒。
func humanizeDuration(d string) string {
dur, err := time.ParseDuration(d)
if err != nil {
return d
}
switch {
case dur >= time.Hour:
return fmt.Sprintf("%d 小時", int(dur.Hours()))
case dur >= time.Minute:
return fmt.Sprintf("%d 分鐘", int(dur.Minutes()))
default:
return "不到 1 分鐘"
}
}
// humanizeFailure 把 collector 的技術錯誤翻成使用者看得懂、而且**知道要不要行動**的一句話。
//
// 🔴 leo 2026-08-06 實測,兩個真實案例(從他 Windows 的 collector.log 挖出來的):
@@ -298,8 +333,22 @@ func humanizeFailure(raw string) string {
return "這份 PDF 看起來是掃描的圖片,沒有文字可以讀 ⇒ 需要先做文字辨識(OCR),或換一份有文字的版本。"
case strings.Contains(raw, "尚未支援的檔案格式"):
return "這種檔案格式還讀不了 ⇒ 先另存成 PDF 或 Word(.docx)再放回同一個資料夾。"
case strings.Contains(raw, "後重試"):
return raw + "(上面是自動重試的排程,真正的原因是前一次失敗)"
}
// 退避訊息:collector 給的是「上次失敗(第 5 次),5h38m38s 後重試|原因:…」。
//
// 🔴 leo 2026-08-06:「**這個錯誤訊息我真的看不懂,明白說比較好**」
// ——我原本只是把原文轉貼、再加一句「上面是自動重試的排程」,那是寫給我自己看的。
// 使用者要的是三件事:**現在怎樣、為什麼、我要不要做什麼**。
if n, wait, reason, ok := parseRetryMessage(raw); ok {
if reason != "" {
// 有原因就以原因為主,排程放後面當補充(他真正需要判斷的是原因)
return humanizeFailure(reason) +
fmt.Sprintf("(已經自動試過 %d 次,約 %s後會再試一次)", n, wait)
}
return fmt.Sprintf("已經自動試過 %d 次都沒成功,約 %s後會再試一次。"+
"這個檔是在舊版失敗的,當時沒有記下原因;如果一直沒好,"+
"到「版本與更新」旁邊把紀錄檔傳給我們。", n, wait)
}
return raw
}