feat: complete T-533 incremental generation

This commit is contained in:
chengma
2026-07-06 17:57:39 +08:00
parent be182b91cf
commit 46995b301d
8 changed files with 434 additions and 72 deletions
+94 -34
View File
@@ -230,18 +230,57 @@ def _task_attempt_count(task, field):
return 0
def is_generatable_task(task):
"""判断任务是否能由② AI生成执行或重试。"""
def _has_generated_title(task):
return bool(str(getattr(task, "new_title", "") or "").strip())
def _has_generated_cover(task):
return bool(str(getattr(task, "new_cover_path", "") or "").strip())
def generation_needs(task, generate_cover=False):
"""Return which AI components still need generation for this task."""
stage = str(getattr(task, "stage", "") or "")
status = str(getattr(task, "status", "") or "")
if status in {"running", "skipped"}:
return False
if stage == "collected":
return True
if stage == "generated" and status == "failed":
return _task_attempt_count(task, "apply_attempts") == 0
return False
return {"title": False, "cover": False}
if stage not in {"collected", "generated"}:
return {"title": False, "cover": False}
if stage == "generated" and _task_attempt_count(task, "apply_attempts") > 0:
return {"title": False, "cover": False}
needs_title = not _has_generated_title(task)
needs_cover = bool(generate_cover) and not _has_generated_cover(task)
return {"title": needs_title, "cover": needs_cover}
def is_generatable_task(task, generate_cover=False):
"""判断任务是否能由② AI生成执行或重试。"""
needs = generation_needs(task, generate_cover=generate_cover)
return bool(needs["title"] or needs["cover"])
def generation_component_totals(tasks, generate_cover=False):
"""Count task and component gaps for a generation run."""
eligible = [
task for task in list(tasks)
if is_generatable_task(task, generate_cover=generate_cover)
]
title_total = 0
cover_total = 0
for task in eligible:
needs = generation_needs(task, generate_cover=generate_cover)
if needs["title"]:
title_total += 1
if needs["cover"]:
cover_total += 1
return {
"total": len(eligible),
"title_total": title_total,
"cover_total": cover_total,
}
def generate_batch(tasks, prompts, ai_cfg=None, on_progress=None, should_stop=None):
@@ -278,14 +317,27 @@ def generate_batch(tasks, prompts, ai_cfg=None, on_progress=None, should_stop=No
should_stop = should_stop or (lambda: False)
eligible = [
task for task in list(tasks)
if is_generatable_task(task)
if is_generatable_task(task, generate_cover=generate_cover)
]
needs_by_task = {
getattr(task, "id", None): generation_needs(task, generate_cover=generate_cover)
for task in eligible
}
title_tasks = [
task for task in eligible
if needs_by_task.get(getattr(task, "id", None), {}).get("title")
]
cover_candidates = [
task for task in eligible
if needs_by_task.get(getattr(task, "id", None), {}).get("cover")
]
summary = {
"ok": True,
"total": len(eligible),
"title_total": len(title_tasks),
"title_done": 0,
"cover_done": 0,
"cover_total": len(eligible) if generate_cover else 0,
"cover_total": len(cover_candidates) if generate_cover else 0,
"generated_done": 0,
"failed": 0,
"cancelled": False,
@@ -331,7 +383,7 @@ def generate_batch(tasks, prompts, ai_cfg=None, on_progress=None, should_stop=No
max_workers=max(1, int(generation_cfg.get("title_concurrency", 1)))
) as executor:
futures = {}
for task in eligible:
for task in title_tasks:
if should_stop():
summary["cancelled"] = True
break
@@ -361,29 +413,31 @@ def generate_batch(tasks, prompts, ai_cfg=None, on_progress=None, should_stop=No
summary["title_done"] += 1
set_step(task, "title_done")
_emit_generation_event(on_event, task, "title", "title_done", "success")
if not generate_cover:
set_step(task, "db_write")
_emit_generation_event(on_event, task, "title", "db_write", "start")
db.set_generated(task.id, new_title, None, path=db_path)
set_step(task, "db_write")
_emit_generation_event(on_event, task, "title", "db_write", "start")
existing_cover_path = getattr(task, "new_cover_path", None)
db.set_generated(task.id, new_title, existing_cover_path, path=db_path)
needs_cover = needs_by_task.get(getattr(task, "id", None), {}).get("cover")
if not needs_cover:
summary["generated_done"] += 1
if on_task_update is not None:
on_task_update(
task.id,
{
"stage": "generated",
"status": "success",
"new_title": new_title,
"new_cover_path": None,
},
)
_emit_generation_event(
on_event,
task,
"title",
"db_write",
"success",
detail="仅生成标题",
if on_task_update is not None:
on_task_update(
task.id,
{
"stage": "generated",
"status": "success",
"new_title": new_title,
"new_cover_path": existing_cover_path,
},
)
_emit_generation_event(
on_event,
task,
"title",
"db_write",
"success",
detail="标题已保存,等待封面" if needs_cover else "仅生成标题",
)
except CancelledError:
summary["cancelled"] = True
_emit_generation_event(on_event, task, "title", get_step(task, "title_request"), "cancelled", level="warning")
@@ -401,9 +455,15 @@ def generate_batch(tasks, prompts, ai_cfg=None, on_progress=None, should_stop=No
summary["ok"] = False
return summary
for task in eligible:
task_id = getattr(task, "id", None)
if task_id not in title_results and _has_generated_title(task):
title_results[task_id] = str(getattr(task, "new_title") or "")
_emit_generation_event(on_event, task, "title", "title_submit", "skipped", detail="已有标题")
cover_tasks = [
task for task in eligible
if task.id in title_results
task for task in cover_candidates
if getattr(task, "id", None) in title_results
]
with ThreadPoolExecutor(
max_workers=max(1, int(generation_cfg.get("image_concurrency", 1)))