feat: polish generate run logs

This commit is contained in:
chengma
2026-07-08 12:07:38 +08:00
parent 089a2f5545
commit 40cf79b930
3 changed files with 224 additions and 13 deletions
+108 -11
View File
@@ -2,7 +2,18 @@
from __future__ import annotations
import datetime as _dt
import re
from .widgets import *
_USER_LOG_URL_RE = re.compile(r"https?://[^\s,,;;))\]]+", re.IGNORECASE)
_USER_LOG_PATH_RE = re.compile(
r"(?i)(/api(?:/v\d+)?/[^\s,,;;))\]]*|/generated/images/[^\s,,;;))\]]*)"
)
class GenerateWorker(BaseWorker):
"""Generate titles and covers for eligible collected or failed generation tasks."""
@@ -28,8 +39,12 @@ class GenerateWorker(BaseWorker):
self._cmhub_points_balance = None
self._billing_error = None
self._billing_stop_requested = False
self._run_started_at_text = ""
self._run_started_monotonic = None
def execute(self):
self._run_started_at_text = self._format_local_time()
self._run_started_monotonic = time.monotonic()
account_rows = accounts.list_accounts(path=self.db_path, config=self.config)
account_by_alias = {
str(account.alias).strip(): account
@@ -57,7 +72,7 @@ class GenerateWorker(BaseWorker):
if generate_cover:
if appconfig.ai_backend(self.config) == "cmhub":
concurrency = ai.cmhub_image_concurrency_plan(ai_cfg)
start_message = "[开始] 本轮生成 {total} 条:标题{title_total},图片{cover_total};标题并发{title_concurrency},图片并发{image_concurrency},cmhub实际生图并发{request_concurrency},下载并发{download_concurrency}".format(
start_message = "[开始] 本轮生成 {total} 条:标题{title_total},图片{cover_total};标题并发{title_concurrency},图片并发{image_concurrency},cmhub实际生图并发{request_concurrency},下载并发{download_concurrency};开始时间 {started_at}".format(
total=len(eligible),
title_total=component_totals["title_total"],
cover_total=component_totals["cover_total"],
@@ -65,20 +80,23 @@ class GenerateWorker(BaseWorker):
image_concurrency=concurrency["configured_image_concurrency"],
request_concurrency=concurrency["request_concurrency"],
download_concurrency=concurrency["download_concurrency"],
started_at=self._run_started_at_text,
)
else:
start_message = "[开始] 本轮生成 {total} 条:标题{title_total},图片{cover_total};标题并发{title_concurrency},图片并发{image_concurrency}".format(
start_message = "[开始] 本轮生成 {total} 条:标题{title_total},图片{cover_total};标题并发{title_concurrency},图片并发{image_concurrency};开始时间 {started_at}".format(
total=len(eligible),
title_total=component_totals["title_total"],
cover_total=component_totals["cover_total"],
title_concurrency=ai_cfg.get("title_concurrency", 1),
image_concurrency=ai_cfg.get("image_concurrency", 1),
started_at=self._run_started_at_text,
)
else:
start_message = "[开始] 本轮生成 {total} 条:本轮仅生成标题,不生成图片;标题{title_total};标题并发{title_concurrency}".format(
start_message = "[开始] 本轮生成 {total} 条:本轮仅生成标题,不生成图片;标题{title_total};标题并发{title_concurrency};开始时间 {started_at}".format(
total=len(eligible),
title_total=component_totals["title_total"],
title_concurrency=ai_cfg.get("title_concurrency", 1),
started_at=self._run_started_at_text,
)
self._log_run_event(start_message)
try:
@@ -113,8 +131,9 @@ class GenerateWorker(BaseWorker):
"cancelled": self.should_cancel(),
"generate_cover": generate_cover,
}
user_error = self._user_log_detail(error) or "未知错误"
self._log_run_event(
f"[失败] AI 生成运行失败:{error}",
f"[失败] AI 生成运行失败:{user_error}",
level="error",
)
self._write_diagnostic_log(
@@ -179,7 +198,13 @@ class GenerateWorker(BaseWorker):
phase = payload.get("phase") or "generate"
step = payload.get("step") or "unknown"
result = payload.get("result") or "start"
detail = self._short_detail(payload.get("detail"))
detail = self._user_log_detail(
payload.get("detail"),
phase=phase,
step=step,
code=payload.get("code"),
status=payload.get("status"),
)
if isinstance(payload.get("metadata"), dict):
return self._format_cmhub_billing_event(task, phase, payload.get("metadata"))
if phase == "title":
@@ -206,7 +231,7 @@ class GenerateWorker(BaseWorker):
if result == "start" and step == "cover_submit":
return f"[图片] {self._task_progress_label(task)} 开始生成"
if result == "success" and step == "cover_request":
return f"[图片] {self._task_progress_label(task)} {detail or 'cmhub 已返回,耗时未知'}"
return f"[图片] {self._task_progress_label(task)} {detail or 'cmhub 已返回图片,耗时未知'}"
if result == "warning" and step == "cover_download":
return f"[图片] {self._task_progress_label(task)} {detail or '图片下载较慢'}"
if result == "success" and step == "cover_download":
@@ -214,7 +239,7 @@ class GenerateWorker(BaseWorker):
if result == "success" and step == "cover_save":
return f"[图片] {self._task_progress_label(task)} 本地保存完成,{detail or '耗时未知'}"
if result == "success" and step == "db_write":
suffix = f",已保存 {detail}" if detail else ""
suffix = ",已保存到本地" if detail else ""
return f"[图片] {self._task_progress_label(task)} 成功{suffix}"
if result == "retry":
return self._retry_message("图片", task, payload, detail)
@@ -285,16 +310,88 @@ class GenerateWorker(BaseWorker):
return text[:1997] + "..."
return text
def _user_log_detail(self, detail, phase=None, step=None, code=None, status=None):
if detail is None:
return ""
raw = diagnostics.redact_log_text(str(detail)).replace("\r", " ").replace("\n", " ").strip()
lowered = raw.lower()
code_text = str(code or "").strip().lower()
if code_text == "insufficient_points" or "点数不足" in raw:
return "点数不足,请先充值"
if code_text == "unauthorized" or "api key 无效" in raw.lower():
return "cmhub API Key 无效,请去⑤设置重填"
if code_text in {"model_not_allowed", "no_pricing_rule"} or "模型别名" in raw or "模型配置不可用" in raw:
return "cmhub 模型别名不可用,请去⑤设置刷新别名并保存"
if code_text == "content_blocked" or "内容安全" in raw:
return "cmhub 内容安全策略拒绝本次生成"
if code_text == "rate_limited" or "rate_limited" in lowered or "请求过于频繁" in raw:
return "cmhub 请求过于频繁,请稍后重试"
if code_text == "connect_timeout" or "连接 cmhub 超时" in raw:
return "连接 cmhub 超时,请检查网络或稍后重试"
if code_text == "read_timeout" or "等待 cmhub 返回超时" in raw:
return "等待 cmhub 返回超时,本条已失败;可稍后重试"
if (
code_text == "not_found"
or "not_found" in lowered
or "接口不存在" in raw
or str(status or "") == "404"
):
return "cmhub 网关接口不可用,请检查⑤设置中的 Base URL,或联系服务方确认网关版本"
if code_text == "upstream_error" or "upstream_error" in lowered or "上游" in raw:
return "cmhub 上游生成失败,请稍后重试"
if "下载 cmhub 图片失败" in raw:
return "下载 cmhub 图片失败,请检查网络后稍后重试"
text = raw.replace("image_url", "图片")
text = text.replace("返回 图片", "返回图片")
text = _USER_LOG_URL_RE.sub("[链接已隐藏]", text)
text = _USER_LOG_PATH_RE.sub("[接口路径已隐藏]", text)
text = text.replace("GET [链接已隐藏]", "请求 cmhub")
text = text.replace("POST [链接已隐藏]", "请求 cmhub")
if len(text) > 180:
return text[:177] + "..."
return text
def _format_generate_completion(self, summary):
progress = self._summary_text(summary)
billing_error = summary.get("billing_error") or {}
finished_at = self._format_local_time()
elapsed = self._format_run_elapsed()
suffix = f";{self._completion_time_label(summary)} {finished_at},总用时 {elapsed}"
if billing_error:
return f"[失败] AI 生成已中止:{billing_error.get('message') or '点数不足,请先充值'},{progress}"
message = self._user_log_detail(billing_error.get("message") or "点数不足,请先充值") or "点数不足,请先充值"
return f"[失败] AI 生成已中止:{message},{progress}{suffix}"
if summary.get("cancelled"):
return f"[停止] AI 生成已停止:{progress}"
return f"[停止] AI 生成已停止:{progress}{suffix}"
if summary.get("error"):
return f"[失败] AI 生成失败:{summary.get('error')},{progress}"
return f"[完成] AI 生成完成:{progress}"
error = self._user_log_detail(summary.get("error")) or "未知错误"
return f"[失败] AI 生成失败:{error},{progress}{suffix}"
return f"[完成] AI 生成完成:{progress}{suffix}"
def _completion_time_label(self, summary):
if summary.get("billing_error") or summary.get("error"):
return "失败时间"
if summary.get("cancelled"):
return "停止时间"
return "完成时间"
def _format_local_time(self):
return _dt.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
def _format_run_elapsed(self):
if self._run_started_monotonic is None:
return "0秒"
return self._format_duration(time.monotonic() - self._run_started_monotonic)
def _format_duration(self, seconds):
total = max(0, int(seconds or 0))
hours, remainder = divmod(total, 3600)
minutes, seconds = divmod(remainder, 60)
if hours:
return f"{hours}小时{minutes:02d}分{seconds:02d}秒"
if minutes:
return f"{minutes}分{seconds:02d}秒"
return f"{seconds}秒"
def _summary_text(self, summary):
title_total = summary.get("title_total", summary.get("total", 0))