feat: complete T-528 cmhub billing feedback
This commit is contained in:
+85
-6
@@ -24,6 +24,10 @@ class GenerateWorker(BaseWorker):
|
||||
self._account_by_alias = {}
|
||||
self._task_positions = {}
|
||||
self._eligible_total = 0
|
||||
self._last_progress_payload = {}
|
||||
self._cmhub_points_balance = None
|
||||
self._billing_error = None
|
||||
self._billing_stop_requested = False
|
||||
|
||||
def execute(self):
|
||||
account_rows = accounts.list_accounts(path=self.db_path, config=self.config)
|
||||
@@ -72,8 +76,8 @@ class GenerateWorker(BaseWorker):
|
||||
"on_error": self._on_generation_error,
|
||||
"generate_cover": generate_cover,
|
||||
},
|
||||
on_progress=self.progress.emit,
|
||||
should_stop=self.should_cancel,
|
||||
on_progress=self._emit_generate_progress,
|
||||
should_stop=self._should_stop_generation,
|
||||
)
|
||||
except Exception as exc:
|
||||
error = diagnostics.redact_log_text(str(exc) or exc.__class__.__name__)
|
||||
@@ -100,30 +104,59 @@ class GenerateWorker(BaseWorker):
|
||||
payload={"error": error},
|
||||
exc=exc,
|
||||
)
|
||||
if self._cmhub_points_balance is not None:
|
||||
summary["points_balance"] = self._cmhub_points_balance
|
||||
if self._billing_error is not None:
|
||||
summary["billing_error"] = dict(self._billing_error)
|
||||
summary["ok"] = False
|
||||
summary["cancelled"] = True
|
||||
summary["run_id"] = self._run_id
|
||||
summary["batch_ids"] = batch_ids
|
||||
status = "cancelled" if summary.get("cancelled") else "done"
|
||||
level = "warning" if summary.get("cancelled") or summary.get("error") else "info"
|
||||
status = "failed" if summary.get("billing_error") or summary.get("error") else ("cancelled" if summary.get("cancelled") else "done")
|
||||
level = "error" if summary.get("billing_error") or summary.get("error") else ("warning" if summary.get("cancelled") else "info")
|
||||
self._log_run_event(self._format_generate_completion(summary), level=level)
|
||||
self._finish_run_log(status, summary)
|
||||
return summary
|
||||
|
||||
def _emit_generate_progress(self, payload):
|
||||
progress = dict(payload or {})
|
||||
if self._cmhub_points_balance is not None:
|
||||
progress["points_balance"] = self._cmhub_points_balance
|
||||
if self._billing_error is not None:
|
||||
progress["billing_error"] = dict(self._billing_error)
|
||||
self._last_progress_payload = dict(progress)
|
||||
self.progress.emit(progress)
|
||||
|
||||
def _should_stop_generation(self):
|
||||
return self.should_cancel() or self._billing_stop_requested
|
||||
|
||||
def _emit_row_update(self, task_id, fields):
|
||||
self.row_updated.emit(int(task_id), dict(fields or {}))
|
||||
|
||||
def _on_generation_event(self, payload):
|
||||
task = payload.get("task")
|
||||
self._remember_cmhub_metadata(payload)
|
||||
message = self._format_generation_event(payload)
|
||||
if not message:
|
||||
return
|
||||
self._log_run_event(message, task=task, level=payload.get("level") or "info")
|
||||
|
||||
def _remember_cmhub_metadata(self, payload):
|
||||
metadata = payload.get("metadata")
|
||||
if not isinstance(metadata, dict):
|
||||
return
|
||||
if metadata.get("points_balance") is not None:
|
||||
self._cmhub_points_balance = metadata.get("points_balance")
|
||||
self._emit_generate_progress(self._last_progress_payload)
|
||||
|
||||
def _format_generation_event(self, payload):
|
||||
task = payload.get("task")
|
||||
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"))
|
||||
if isinstance(payload.get("metadata"), dict):
|
||||
return self._format_cmhub_billing_event(task, phase, payload.get("metadata"))
|
||||
if phase == "title":
|
||||
if result == "start" and step == "title_submit":
|
||||
return f"[标题] {self._task_progress_label(task)} 开始生成"
|
||||
@@ -154,6 +187,22 @@ class GenerateWorker(BaseWorker):
|
||||
return None
|
||||
return None
|
||||
|
||||
def _format_cmhub_billing_event(self, task, phase, metadata):
|
||||
label = "标题" if phase == "title" else ("图片" if phase == "cover" else "AI")
|
||||
parts = []
|
||||
alias = metadata.get("alias") or metadata.get("model_used")
|
||||
if alias:
|
||||
parts.append(f"别名 {alias}")
|
||||
if metadata.get("points_cost") is not None:
|
||||
parts.append(f"扣点 {metadata.get('points_cost')}")
|
||||
if metadata.get("points_balance") is not None:
|
||||
parts.append(f"余额 {metadata.get('points_balance')}")
|
||||
if metadata.get("call_id"):
|
||||
parts.append(f"call_id={metadata.get('call_id')}")
|
||||
if not parts:
|
||||
return None
|
||||
return f"[计费] {self._task_plain_label(task)} {label}生成:" + ",".join(str(part) for part in parts)
|
||||
|
||||
def _retry_message(self, label, task, payload, detail):
|
||||
attempt = int(payload.get("attempt", 0) or 0)
|
||||
attempts = int(payload.get("attempts", 0) or 0)
|
||||
@@ -192,6 +241,9 @@ class GenerateWorker(BaseWorker):
|
||||
|
||||
def _format_generate_completion(self, summary):
|
||||
progress = self._summary_text(summary)
|
||||
billing_error = summary.get("billing_error") or {}
|
||||
if billing_error:
|
||||
return f"[失败] AI 生成已中止:{billing_error.get('message') or '点数不足,请先充值'},{progress}"
|
||||
if summary.get("cancelled"):
|
||||
return f"[停止] AI 生成已停止:{progress}"
|
||||
if summary.get("error"):
|
||||
@@ -212,14 +264,40 @@ class GenerateWorker(BaseWorker):
|
||||
task = payload.get("task")
|
||||
phase = payload.get("phase") or "generate"
|
||||
step = payload.get("step") or "unknown"
|
||||
exception = payload.get("exception")
|
||||
code = payload.get("code") or getattr(exception, "code", None)
|
||||
status = payload.get("status") or getattr(exception, "status", None)
|
||||
error = diagnostics.redact_log_text(payload.get("error") or "未知错误")
|
||||
if str(code or "") == "insufficient_points":
|
||||
self._billing_stop_requested = True
|
||||
message = "点数不足,请先充值。本轮未开始任务将停止。"
|
||||
self._billing_error = {
|
||||
"code": "insufficient_points",
|
||||
"message": message,
|
||||
"phase": phase,
|
||||
"task_id": getattr(task, "id", None),
|
||||
"item_id": getattr(task, "item_id", None),
|
||||
}
|
||||
if status is not None:
|
||||
self._billing_error["status"] = status
|
||||
self._log_run_event(
|
||||
f"[计费] {self._task_plain_label(task)} 点数不足,请先充值;本轮未开始任务将停止",
|
||||
task=task,
|
||||
level="error",
|
||||
)
|
||||
self._emit_generate_progress(self._last_progress_payload)
|
||||
diagnostic_payload = {"phase": phase, "error": error}
|
||||
if code is not None:
|
||||
diagnostic_payload["code"] = str(code)
|
||||
if status is not None:
|
||||
diagnostic_payload["status"] = status
|
||||
self._write_diagnostic_log(
|
||||
"AI生成任务失败",
|
||||
level="ERROR",
|
||||
step=step,
|
||||
task=task,
|
||||
payload={"phase": phase, "error": error},
|
||||
exc=payload.get("exception"),
|
||||
payload=diagnostic_payload,
|
||||
exc=exception,
|
||||
)
|
||||
|
||||
def _batch_ids(self, tasks):
|
||||
@@ -245,6 +323,7 @@ class GenerateWorker(BaseWorker):
|
||||
"title_concurrency": ai_cfg.get("title_concurrency"),
|
||||
"image_concurrency": ai_cfg.get("image_concurrency"),
|
||||
"generate_cover": ai_cfg.get("generate_cover", False),
|
||||
"backend": ai_cfg.get("backend", "direct"),
|
||||
},
|
||||
path=self.db_path,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user