T-568 支持封面画廊单任务重生
This commit is contained in:
+239
-3
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user