T-568 支持封面画廊单任务重生

This commit is contained in:
chengma
2026-07-09 11:49:06 +08:00
parent a391d2db99
commit ae4daacbbf
3 changed files with 527 additions and 5 deletions
+239 -3
View File
@@ -73,18 +73,40 @@ class CoverGalleryDialog(QDialog):
THUMBNAIL_SIZE = 180
OLD_THUMBNAIL_SIZE = 300
def __init__(self, task, image_root, db_path, account=None, parent=None):
def __init__(
self,
task,
image_root,
db_path,
account=None,
parent=None,
config=None,
prompt_values=None,
diagnostic_log_dir=None,
bulk_running_checker=None,
regenerate_running_callback=None,
gallery_updated_callback=None,
):
super().__init__(parent)
self.task = task
self.image_root = image_root
self.db_path = db_path
self.account = account
self.config = config or {}
self.prompt_values = dict(prompt_values or {})
self.diagnostic_log_dir = diagnostic_log_dir
self.bulk_running_checker = bulk_running_checker
self.regenerate_running_callback = regenerate_running_callback
self.gallery_updated_callback = gallery_updated_callback
self.current_path = _normalize_file_path(getattr(task, "new_cover_path", None))
self.selected_path = None
self.changed = False
self.candidate_buttons = {}
self.button_group = QButtonGroup(self)
self.button_group.setExclusive(True)
self.regenerate_worker = None
self.regenerate_thread = None
self._regenerate_last_error = ""
self.candidates = image_paths.list_task_cover_candidates(image_root, task, account=account)
self.setWindowTitle(f"封面画廊:{getattr(task, 'item_id', '')}")
@@ -95,6 +117,9 @@ class CoverGalleryDialog(QDialog):
layout.addLayout(body_layout, 1)
button_layout = QHBoxLayout()
self.regenerate_button = QPushButton("重新生成")
self.regenerate_button.clicked.connect(self.regenerate_cover)
button_layout.addWidget(self.regenerate_button)
button_layout.addStretch(1)
self.save_button = QPushButton("保存")
self.cancel_button = QPushButton("取消")
@@ -106,6 +131,7 @@ class CoverGalleryDialog(QDialog):
self._sync_initial_selection()
self._update_status()
self._update_regenerate_button_state()
self._fit_to_screen()
def _old_cover_panel(self):
@@ -247,6 +273,67 @@ class CoverGalleryDialog(QDialog):
self.accept()
return True
def regenerate_cover(self, checked=False):
if self.regenerate_thread is not None:
return False
if self._bulk_generate_running():
self.status_label.setText("②批量生成正在运行,完成后才能重新生成单个封面")
self._update_regenerate_button_state()
return False
if not str(getattr(self.task, "new_title", "") or "").strip():
QMessageBox.warning(self, "重新生成封面", "当前任务缺少新标题,不能单独重新生成封面")
return False
if not self._confirm_regenerate():
return False
try:
db.reset_generated(
self.task.id,
reset_title=False,
reset_cover=True,
path=self.db_path,
)
fresh_task = db.get_task(self.task.id, path=self.db_path)
except Exception as exc:
QMessageBox.warning(self, "重新生成封面", str(exc))
self.status_label.setText(f"重新生成封面失败:{exc}")
return False
if fresh_task is None:
QMessageBox.warning(self, "重新生成封面", "任务不存在或已删除")
self.status_label.setText("重新生成封面失败:任务不存在或已删除")
return False
self._reload_task_and_candidates(fresh_task)
self.status_label.setText(f"开始重新生成商品 {getattr(fresh_task, 'item_id', '')} 的封面图片")
worker = GenerateWorker(
[fresh_task],
self.prompt_values,
db_path=self.db_path,
config=_cover_regenerate_config(self.config),
diagnostic_log_dir=self.diagnostic_log_dir,
)
worker.progress.connect(self._on_regenerate_progress)
worker.row_updated.connect(self._on_regenerate_row_updated)
worker.log.connect(self._on_regenerate_log)
worker.failed.connect(self._on_regenerate_failed)
worker.finished.connect(self._on_regenerate_finished)
worker.cancelled.connect(self._on_regenerate_cancelled)
thread = run_worker(worker, thread_name="CoverRegenerateWorker", start=False)
thread.finished.connect(lambda: self._forget_regenerate_thread(thread))
self.regenerate_worker = worker
self.regenerate_thread = thread
self._set_regenerate_running(True)
thread.start()
return True
def _confirm_regenerate(self):
box = QMessageBox(self)
box.setWindowTitle("重新生成封面")
box.setText("将重新生成当前商品封面,可能消耗 cmhub 点数。")
confirm_button = box.addButton("重新生成", QMessageBox.AcceptRole)
cancel_button = box.addButton("取消", QMessageBox.RejectRole)
box.setDefaultButton(cancel_button)
box.exec()
return box.clickedButton() is confirm_button
def _needs_committed_confirmation(self):
return int(getattr(self.task, "committed", 0) or 0) == 1 or getattr(self.task, "stage", None) == "applied"
@@ -266,6 +353,105 @@ class CoverGalleryDialog(QDialog):
dialog = OriginalImageDialog(image_path, self)
dialog.exec()
def _bulk_generate_running(self):
return bool(self.bulk_running_checker and self.bulk_running_checker())
def _set_regenerate_running(self, running):
self.save_button.setEnabled(not running and self.selected_path is not None)
self.regenerate_button.setEnabled(not running and not self._bulk_generate_running())
self.regenerate_button.setToolTip(
"②批量生成正在运行,完成后才能重新生成单个封面"
if self._bulk_generate_running()
else ("正在重新生成当前商品封面" if running else "")
)
if self.regenerate_running_callback is not None:
self.regenerate_running_callback(bool(running))
def _update_regenerate_button_state(self):
running = self.regenerate_thread is not None
self.regenerate_button.setEnabled(not running and not self._bulk_generate_running())
self.regenerate_button.setToolTip(
"②批量生成正在运行,完成后才能重新生成单个封面"
if self._bulk_generate_running()
else ("正在重新生成当前商品封面" if running else "")
)
def _reload_task_and_candidates(self, task=None):
if task is not None:
self.task = task
self.current_path = _normalize_file_path(getattr(self.task, "new_cover_path", None))
self.selected_path = None
self.candidates = image_paths.list_task_cover_candidates(self.image_root, self.task, account=self.account)
self._rebuild_candidate_items()
self._sync_initial_selection()
self._update_status()
def _rebuild_candidate_items(self):
_clear_layout(self.candidate_layout)
self.candidate_buttons = {}
self.button_group = QButtonGroup(self)
self.button_group.setExclusive(True)
if self.candidates:
for index, candidate_path in enumerate(self.candidates):
self.candidate_layout.addWidget(self._candidate_item(candidate_path, index))
self.candidate_layout.addStretch(1)
else:
empty_label = QLabel("暂无生成封面图片")
empty_label.setObjectName("coverGalleryEmptyLabel")
empty_label.setMinimumSize(320, self.THUMBNAIL_SIZE)
empty_label.setAlignment(Qt.AlignCenter)
self.candidate_layout.addWidget(empty_label)
def _on_regenerate_progress(self, payload):
if payload.get("failed"):
self.status_label.setText("封面重新生成失败,历史候选仍可选择保存")
def _on_regenerate_row_updated(self, task_id, fields):
if self.gallery_updated_callback is not None:
self.gallery_updated_callback(self.task)
def _on_regenerate_log(self, message):
text = str(message or "").strip()
if not text:
return
if text.startswith("[失败]"):
self._regenerate_last_error = text
self.status_label.setText(text)
elif "成功" in text or "完成" in text:
self.status_label.setText(text)
def _on_regenerate_failed(self, task_id, error):
self._regenerate_last_error = str(error or "未知错误")
self.status_label.setText(f"封面重新生成失败:{self._regenerate_last_error}")
def _on_regenerate_finished(self, summary):
self._finish_regenerate(summary, cancelled=False)
def _on_regenerate_cancelled(self, summary):
self._finish_regenerate(summary, cancelled=True)
def _finish_regenerate(self, summary, cancelled=False):
fresh_task = db.get_task(self.task.id, path=self.db_path)
if fresh_task is not None:
self._reload_task_and_candidates(fresh_task)
self._set_regenerate_running(False)
if self.gallery_updated_callback is not None:
self.gallery_updated_callback(self.task)
if cancelled or summary.get("cancelled"):
self.status_label.setText("封面重新生成已停止,历史候选仍可选择保存")
return
if summary.get("ok") and summary.get("cover_done", 0) > 0:
self.status_label.setText("封面重新生成完成")
return
detail = self._regenerate_last_error or summary.get("error") or "请稍后重试"
self.status_label.setText(f"封面重新生成失败:{detail}")
def _forget_regenerate_thread(self, thread):
if self.regenerate_thread is thread:
self.regenerate_thread = None
self.regenerate_worker = None
self._set_regenerate_running(False)
def _fit_to_screen(self):
available = _available_geometry()
if available is None:
@@ -281,6 +467,23 @@ def _load_image(image_path):
return QImage(str(image_path or ""))
def _clear_layout(layout):
while layout.count():
item = layout.takeAt(0)
widget = item.widget()
if widget is not None:
widget.setParent(None)
widget.deleteLater()
def _cover_regenerate_config(config):
runtime_config = dict(config or {})
ai_settings = appconfig.ai_config(runtime_config)
ai_settings["generate_cover"] = True
runtime_config["ai"] = ai_settings
return runtime_config
def _build_image_label(image_path, size, empty_text):
label = QLabel()
label.setAlignment(Qt.AlignCenter)
@@ -391,6 +594,8 @@ class GenerateTab(QWidget):
self.current_cover_template = None
self.generate_worker = None
self.generate_thread = None
self.generate_running = False
self.cover_gallery_regenerate_running = False
self._title_elapsed_start = None
self._cover_elapsed_start = None
self._title_elapsed_seconds = 0
@@ -948,6 +1153,9 @@ class GenerateTab(QWidget):
self._set_status("封面提示词预览已生成")
def start_generate(self, checked=False):
if self.cover_gallery_regenerate_running:
self._set_status("封面画廊正在重新生成,完成后再开始批量生成")
return
if self.generate_thread is not None:
self._set_status("AI 生成正在进行...")
return
@@ -1219,6 +1427,15 @@ class GenerateTab(QWidget):
db_path=self.db_path,
account=account,
parent=self,
config=self.config,
prompt_values={
"title": self.title_prompt_edit.toPlainText(),
"cover": self.cover_prompt_edit.toPlainText(),
},
diagnostic_log_dir=diagnostics.DEFAULT_LOG_DIR,
bulk_running_checker=lambda: self.generate_thread is not None,
regenerate_running_callback=self._set_cover_gallery_regenerate_running,
gallery_updated_callback=self._on_cover_gallery_updated,
)
if dialog.exec() == QDialog.Accepted:
self.refresh_tasks()
@@ -1228,9 +1445,10 @@ class GenerateTab(QWidget):
self._set_status(f"当前新封面未变更:{task.item_id}")
def _set_generate_running(self, running):
self.generate_button.setEnabled(not running)
self.generate_running = bool(running)
self.generate_button.setEnabled(not running and not self.cover_gallery_regenerate_running)
self.stop_generate_button.setEnabled(running)
self.reset_generate_button.setEnabled(not running)
self.reset_generate_button.setEnabled(not running and not self.cover_gallery_regenerate_running)
self.refresh_button.setEnabled(not running)
self.batch_filter.setEnabled(not running)
self.shop_filter.setEnabled(not running)
@@ -1244,6 +1462,24 @@ class GenerateTab(QWidget):
self.rename_cover_template_action.setEnabled(not running)
self.delete_cover_template_action.setEnabled(not running)
self.generate_cover_checkbox.setEnabled(not running)
self._sync_cover_gallery_regenerate_guard()
def _set_cover_gallery_regenerate_running(self, running):
self.cover_gallery_regenerate_running = bool(running)
self._sync_cover_gallery_regenerate_guard()
def _sync_cover_gallery_regenerate_guard(self):
if self.generate_thread is not None or self.generate_running:
return
blocked = bool(self.cover_gallery_regenerate_running)
self.generate_button.setEnabled(not blocked)
self.reset_generate_button.setEnabled(not blocked)
tooltip = "封面画廊正在重新生成,完成后再开始批量生成" if blocked else ""
self.generate_button.setToolTip(tooltip)
self.reset_generate_button.setToolTip(tooltip)
def _on_cover_gallery_updated(self, task):
self.refresh_tasks()
def _forget_generate_thread(self, thread):
if self.generate_thread is thread: