feat(product-suite): complete multi-source recovery

This commit is contained in:
chengma
2026-07-20 18:50:08 +08:00
parent 2e6b93d7e0
commit b803a2a15a
7 changed files with 310 additions and 17 deletions
+126 -13
View File
@@ -676,6 +676,11 @@ class SuiteResultCard(QFrame):
title = QLabel(str(getattr(job, "job_type", "套图")))
title.setStyleSheet("font-weight: 600; color: #24292f;")
title_row.addWidget(title, 1)
source_label = QLabel(_generation_source_label(job))
source_label.setObjectName("suiteResultCardSource")
source_label.setStyleSheet("color: #57606a; font-size: 11px;")
source_label.setToolTip("生成来源:%s" % _generation_source_label(job))
title_row.addWidget(source_label)
if asset is not None and _asset_usable(asset):
delete_button = QToolButton()
delete_button.setText("×")
@@ -791,7 +796,10 @@ class SuiteHistoryImageCard(QFrame):
)
layout.addWidget(image)
footer_text = self._status_text(status)
footer_text = "%s · %s" % (
_generation_source_label(job),
self._status_text(status),
)
if self.retry_count:
footer_text += " · 重试%d次" % self.retry_count
footer = QLabel(footer_text)
@@ -803,6 +811,7 @@ class SuiteHistoryImageCard(QFrame):
tooltip = [
"类型:%s" % str(getattr(job, "job_type", "套图") or "套图"),
"生成来源:%s" % _generation_source_label(job),
"状态:%s" % self._status_text(status),
"生成时间:%s" % _history_time_text(getattr(job, "created_at", "")),
]
@@ -856,6 +865,33 @@ def _history_time_text(value):
return text[:19]
def _generation_source_label(job):
"""Return the persisted job source as a user-facing Chinese label."""
source = str(getattr(job, "generation_source", "") or "").strip().lower()
provider = str(getattr(job, "provider", "") or "").strip().lower()
if (
source == image_studio.GENERATION_SOURCE_CMHUB
and provider == image_studio.PROVIDER_CMHUB
):
return "默认网关"
if (
source == image_studio.GENERATION_SOURCE_DIRECT
and provider == image_studio.PROVIDER_OPENAI_IMAGES_EDITS
):
return "自定义网关"
return "来源未知"
def _generation_source_summary(jobs):
labels = []
for job in jobs:
label = _generation_source_label(job)
if label not in labels:
labels.append(label)
return "、".join(labels) if labels else "来源未知"
class ProductSuiteHistoryDialog(QDialog):
"""Read-only, project-scoped generation history grouped by persisted rounds."""
@@ -1084,6 +1120,10 @@ class ProductSuiteHistoryDialog(QDialog):
"border-radius: 6px; padding: 1px 6px;"
)
header.addWidget(legacy)
source_label = QLabel("来源:%s" % _generation_source_summary(jobs))
source_label.setObjectName("suiteHistoryRoundSource")
source_label.setStyleSheet("color: #57606a;")
header.addWidget(source_label)
header.addStretch(1)
stats = self._round_stats_text(round_info)
stats_label = QLabel(stats)
@@ -1268,9 +1308,10 @@ class SuiteGlobalHistoryThumbnail(QLabel):
self.setAlignment(Qt.AlignCenter)
self.setFixedSize(104, 78)
self.setToolTip(
"%s · %s"
"%s · %s · %s"
% (
str(getattr(job, "job_type", "套图") or "套图"),
_generation_source_label(job),
SuiteHistoryImageCard._status_text(getattr(job, "status", "")),
)
)
@@ -1348,6 +1389,10 @@ class SuiteGlobalHistoryRoundRow(QFrame):
)
meta.addWidget(QLabel("店铺:%s" % account))
meta.addWidget(QLabel(item_text))
source_label = QLabel("来源:%s" % _generation_source_summary(self.jobs))
source_label.setObjectName("suiteGlobalHistorySource")
source_label.setStyleSheet("color: #57606a;")
meta.addWidget(source_label)
layout.addLayout(meta, 0)
thumbnails = QHBoxLayout()
@@ -4227,13 +4272,41 @@ class ProductSuiteTab(QWidget):
return False
@staticmethod
def _is_default_gateway_job(job):
def _has_default_gateway_source(job):
return (
str(getattr(job, "generation_source", "") or "").strip().lower() == "cmhub"
and str(getattr(job, "provider", "") or "").strip().lower() == "cmhub"
and bool(str(getattr(job, "task_id", "") or "").strip())
)
@classmethod
def _is_default_gateway_job(cls, job):
return cls._has_default_gateway_source(job) and bool(
str(getattr(job, "task_id", "") or "").strip()
)
@staticmethod
def _is_direct_gateway_job(job):
return (
str(getattr(job, "generation_source", "") or "").strip().lower()
== image_studio.GENERATION_SOURCE_DIRECT
and str(getattr(job, "provider", "") or "").strip().lower()
== image_studio.PROVIDER_OPENAI_IMAGES_EDITS
)
def _retry_availability(self, job):
if self._has_default_gateway_source(job):
if self._is_default_gateway():
return True, ""
return False, "默认网关图片仅能在⑤设置切换为默认网关后重新生成"
if self._is_direct_gateway_job(job):
if not self._is_direct_gateway():
return False, "自定义网关图片仅能在⑤设置切换为自定义网关后重新生成"
error = self._direct_generation_config_error()
if error:
return False, "自定义网关配置不完整:%s" % error
return True, ""
return False, "当前图片的生成来源无法确认,不能重新生成"
def _resumable_default_gateway_jobs(self, state):
if state is None or state.project_id is None:
return []
@@ -4648,7 +4721,14 @@ class ProductSuiteTab(QWidget):
return
self.start_generation(state)
def start_generation(self, state, specs=None, *, retry_job_id=None):
def start_generation(
self,
state,
specs=None,
*,
retry_job_id=None,
confirm_direct_retry=False,
):
if not self._ensure_generation_gateway():
return False
if state.generation_running():
@@ -4742,6 +4822,9 @@ class ProductSuiteTab(QWidget):
generation_round_key,
template_text,
)
if confirm_direct_retry:
if not self._confirm_direct_retry(state, local_assets, specs):
return False
return self._start_generation_worker(
state,
specs,
@@ -4750,6 +4833,29 @@ class ProductSuiteTab(QWidget):
retry_job_id=retry_job_id,
)
def _confirm_direct_retry(self, state, local_assets, specs):
message = "\n".join(
[
"这张图片上次通过自定义网关生成但未完成。",
"服务商可能对上次未确认请求已计费,本次重新生成可能再次收费。",
"",
self._generation_confirmation_message(
state,
local_assets,
specs,
estimate=None,
),
]
)
return self._confirm(
"确认重新生成商品套图",
message,
destructive=True,
confirm_text="确认重新生成",
cancel_text="取消",
default_cancel=True,
)
def _start_generation_worker(
self,
state,
@@ -5759,8 +5865,8 @@ class ProductSuiteTab(QWidget):
card.retryRequested.connect(self.retry_job)
card.menuRequested.connect(self._show_job_menu)
card.deleteRequested.connect(self.delete_job_asset)
if not self._is_default_gateway():
card.set_retry_enabled(False, "商品套图重新生成仅支持默认网关")
retry_enabled, retry_tooltip = self._retry_availability(job)
card.set_retry_enabled(retry_enabled, retry_tooltip)
self.result_grid.addWidget(card, index // columns, index % columns)
self.result_summary_label.setText("共 %d 张 · 成功 %d 张" % (len(jobs), success))
self.undo_button.setVisible(bool(state.undo_records))
@@ -5831,8 +5937,6 @@ class ProductSuiteTab(QWidget):
ProductSuitePreviewDialog(asset.local_path, "%s预览" % job.job_type, self).exec()
def retry_job(self, job):
if not self._require_default_gateway("商品套图重新生成"):
return
state = self._displayed_state
if state is None:
return
@@ -5846,6 +5950,10 @@ class ProductSuiteTab(QWidget):
}:
self._status("当前图片无需重试", "warning")
return
retry_enabled, retry_tooltip = self._retry_availability(job)
if not retry_enabled:
self._message("当前不可重新生成", retry_tooltip)
return
try:
reference_asset_ids = image_studio.job_reference_asset_ids(job)
except Exception as exc:
@@ -5857,7 +5965,12 @@ class ProductSuiteTab(QWidget):
"job_type": job.job_type,
"prompt": job.prompt,
}
self.start_generation(state, specs=[spec], retry_job_id=job.id)
self.start_generation(
state,
specs=[spec],
retry_job_id=job.id,
confirm_direct_retry=self._is_direct_gateway_job(job),
)
def _show_job_menu(self, job, global_position):
menu = QMenu(self)
@@ -5865,9 +5978,9 @@ class ProductSuiteTab(QWidget):
copy_action = menu.addAction("复制路径")
folder_action = menu.addAction("打开文件夹")
retry_action = menu.addAction("重新生成")
if not self._is_default_gateway():
retry_action.setEnabled(False)
retry_action.setToolTip("商品套图重新生成仅支持默认网关")
retry_enabled, retry_tooltip = self._retry_availability(job)
retry_action.setEnabled(retry_enabled)
retry_action.setToolTip(retry_tooltip)
delete_action = menu.addAction("删除")
action = menu.exec(global_position)
asset = image_studio.get_asset(job.output_asset_id, path=self.db_path) if job.output_asset_id else None