feat: add generate progress and readable logs

This commit is contained in:
chengma
2026-07-02 14:02:53 +08:00
parent 4c7d4fc624
commit f9c6dd3331
7 changed files with 337 additions and 34 deletions
+97 -7
View File
@@ -62,6 +62,14 @@ def gen_title(
cfg,
attempts,
request_kind="json",
on_retry=lambda attempt, total_attempts, exc: _notify_retry(
on_step,
"title_request",
attempt,
total_attempts,
exc,
model,
),
)
_notify_step(on_step, "title_parse_response")
text = _extract_text(data).strip()
@@ -110,11 +118,33 @@ def gen_cover(
attempts,
request_kind="multipart",
content_type=content_type,
on_retry=lambda attempt, total_attempts, exc: _notify_retry(
on_step,
"cover_request",
attempt,
total_attempts,
exc,
model,
),
)
else:
payload = _image_chat_payload(model, cover_prompt, old_cover_path, resolution)
_notify_step(on_step, "cover_request")
data = _call_with_retry(model, payload, cfg, attempts, request_kind="json")
data = _call_with_retry(
model,
payload,
cfg,
attempts,
request_kind="json",
on_retry=lambda attempt, total_attempts, exc: _notify_retry(
on_step,
"cover_request",
attempt,
total_attempts,
exc,
model,
),
)
_notify_step(on_step, "cover_parse_response")
image_bytes = _extract_image_bytes(data, model, cfg)
@@ -131,6 +161,23 @@ def _notify_step(callback, step):
pass
def _notify_retry(callback, step, attempt, attempts, exc, model):
if callback is None:
return
try:
callback(
{
"step": step,
"result": "retry",
"attempt": attempt,
"attempts": attempts,
"detail": _redact(str(exc), model),
}
)
except Exception:
pass
def generate_batch(tasks, prompts, ai_cfg=None, on_progress=None, should_stop=None):
"""Generate titles first, then covers, and persist each successful task."""
@@ -186,9 +233,25 @@ def generate_batch(tasks, prompts, ai_cfg=None, on_progress=None, should_stop=No
return step_by_task.get(getattr(task, "id", None), fallback)
def step_callback(task, phase):
def callback(step):
set_step(task, step)
_emit_generation_event(on_event, task, phase, step, "start")
def callback(event):
if isinstance(event, dict):
step = event.get("step") or "unknown"
result = event.get("result") or "start"
set_step(task, step)
_emit_generation_event(
on_event,
task,
phase,
step,
result,
detail=event.get("detail"),
level=event.get("level") or ("warning" if result == "retry" else "info"),
attempt=event.get("attempt"),
attempts=event.get("attempts"),
)
return
set_step(task, event)
_emit_generation_event(on_event, task, phase, event, "start")
return callback
with ThreadPoolExecutor(
@@ -300,7 +363,7 @@ def generate_batch(tasks, prompts, ai_cfg=None, on_progress=None, should_stop=No
"new_cover_path": new_cover_path,
},
)
_emit_generation_event(on_event, task, "cover", "db_write", "success")
_emit_generation_event(on_event, task, "cover", "db_write", "success", detail=new_cover_path)
except CancelledError:
summary["cancelled"] = True
_emit_generation_event(on_event, task, "cover", get_step(task, "cover_request"), "cancelled", level="warning")
@@ -401,7 +464,17 @@ def _mark_generate_failed(task, exc, db_path, on_task_update):
return error
def _emit_generation_event(callback, task, phase, step, result, detail=None, level="info"):
def _emit_generation_event(
callback,
task,
phase,
step,
result,
detail=None,
level="info",
attempt=None,
attempts=None,
):
if callback is None:
return
payload = {
@@ -413,6 +486,10 @@ def _emit_generation_event(callback, task, phase, step, result, detail=None, lev
}
if detail is not None:
payload["detail"] = diagnostics.redact_log_text(detail)
if attempt is not None:
payload["attempt"] = attempt
if attempts is not None:
payload["attempts"] = attempts
try:
callback(payload)
except Exception:
@@ -475,7 +552,15 @@ def _headers(model, content_type):
}
def _call_with_retry(model, body, config, attempts, request_kind, content_type=None):
def _call_with_retry(
model,
body,
config,
attempts,
request_kind,
content_type=None,
on_retry=None,
):
last_exc = None
for index in range(attempts):
try:
@@ -484,6 +569,11 @@ def _call_with_retry(model, body, config, attempts, request_kind, content_type=N
last_exc = exc
if index + 1 >= attempts:
break
if on_retry is not None:
try:
on_retry(index + 1, attempts, exc)
except Exception:
pass
time.sleep(min(2.0, 0.4 * (index + 1)))
raise AIError(
"AI 调用失败(已尝试 %s 次): %s"