feat(product-suite): confirm prior successful generations

This commit is contained in:
chengma
2026-07-17 10:47:44 +08:00
parent 9166cb7010
commit 9f55dcd4f0
8 changed files with 349 additions and 4 deletions
+91 -3
View File
@@ -1395,7 +1395,14 @@ class ProductSuiteGlobalHistoryDialog(QDialog):
PAGE_SIZE = 30
def __init__(self, *, current_project_id=None, db_path=None, parent=None):
def __init__(
self,
*,
current_project_id=None,
current_project_only=False,
db_path=None,
parent=None,
):
super().__init__(parent)
self.db_path = db_path
self.current_project_id = None
@@ -1490,6 +1497,7 @@ class ProductSuiteGlobalHistoryDialog(QDialog):
layout.addWidget(self.load_more_button, 0, Qt.AlignHCenter)
self.set_current_project(current_project_id, refresh=False)
self.set_current_project_filter(current_project_only, refresh=False)
self.refresh_history()
def set_current_project(self, project_id, *, refresh=True):
@@ -1512,6 +1520,15 @@ class ProductSuiteGlobalHistoryDialog(QDialog):
if refresh and (changed or self.current_project_checkbox.isChecked()):
self.refresh_history()
def set_current_project_filter(self, enabled, *, refresh=True):
should_filter = bool(enabled) and self.current_project_id is not None
changed = self.current_project_checkbox.isChecked() != should_filter
previous = self.current_project_checkbox.blockSignals(True)
self.current_project_checkbox.setChecked(should_filter)
self.current_project_checkbox.blockSignals(previous)
if refresh and changed:
self.refresh_history()
def refresh_history(self, checked=False):
scroll_value = self.scroll.verticalScrollBar().value()
self._clear_history_content()
@@ -2309,6 +2326,40 @@ class ProductSuiteTab(QWidget):
box.exec()
return box.clickedButton() is confirm_button
def _confirm_new_generation_history(self, state, summary):
item_id = str(state.item_id or "临时草稿")
account_text = self._account_context_label(state)
latest = str(summary.latest_succeeded_at or "").replace("T", " ")
lines = [
"%s" % account_text,
"商品ID:%s" % item_id,
"该商品已有成功套图:%d轮,%d张。"
% (
int(summary.successful_round_count),
int(summary.successful_image_count),
),
]
if latest:
lines.append("最近成功时间:%s" % latest)
lines.append("继续会创建新一轮生成,已有历史结果会保留。")
box = QMessageBox(self)
box.setIcon(QMessageBox.Warning)
box.setWindowTitle("已有套图生成记录")
box.setText("\n".join(lines))
history_button = box.addButton("查看历史", QMessageBox.ActionRole)
continue_button = box.addButton("继续生成新一轮", QMessageBox.AcceptRole)
cancel_button = box.addButton("取消", QMessageBox.RejectRole)
box.setDefaultButton(cancel_button)
box.setEscapeButton(cancel_button)
box.exec()
clicked = box.clickedButton()
if clicked is history_button:
return "history"
if clicked is continue_button:
return "continue"
return "cancel"
def refresh_accounts(self):
selected = self.account_combo.currentData()
try:
@@ -4192,6 +4243,31 @@ class ProductSuiteTab(QWidget):
spec["generation_slot_index"] = original_job.generation_slot_index
generation_round_key = str(original_job.generation_round_key or "")
else:
if confirm_batch:
try:
history_summary = image_studio.get_successful_generation_history_summary(
state.project_id,
path=self.db_path,
)
except Exception:
self._message(
"读取套图历史失败",
"暂时无法确认该商品是否已有成功套图,请稍后重试。",
)
return False
if history_summary.successful_round_count:
decision = self._confirm_new_generation_history(
state,
history_summary,
)
if decision == "history":
self.open_history_dialog(
current_project_only=True,
current_project_id=state.project_id,
)
return False
if decision != "continue":
return False
generation_round_key = uuid.uuid4().hex
for slot_index, spec in enumerate(specs):
spec["generation_round_key"] = generation_round_key
@@ -4910,14 +4986,25 @@ class ProductSuiteTab(QWidget):
self.result_summary_label.setText("共 %d 张 · 成功 %d 张" % (len(jobs), success))
self.undo_button.setVisible(bool(state.undo_records))
def open_history_dialog(self, checked=False):
def open_history_dialog(
self,
checked=False,
*,
current_project_only=False,
current_project_id=None,
):
state = self._displayed_state
current_project_id = state.project_id if state is not None else None
if current_project_id is None:
current_project_id = state.project_id if state is not None else None
dialog = self._history_dialog
if dialog is not None:
try:
dialog.set_current_project(current_project_id, refresh=False)
dialog.set_current_project_filter(
current_project_only,
refresh=False,
)
dialog.refresh_history()
dialog.show()
dialog.raise_()
@@ -4929,6 +5016,7 @@ class ProductSuiteTab(QWidget):
dialog = ProductSuiteGlobalHistoryDialog(
current_project_id=current_project_id,
current_project_only=current_project_only,
db_path=self.db_path,
parent=self,
)
+60
View File
@@ -151,6 +151,16 @@ class ImageStudioHistoryRound:
is_legacy: bool
@dataclass(frozen=True)
class ImageStudioSuccessfulGenerationHistorySummary:
"""Successful persisted output summary for one active product project."""
project_id: int
successful_round_count: int
successful_image_count: int
latest_succeeded_at: Optional[str]
class ImageStudioError(RuntimeError):
"""Raised when the AI image studio service cannot complete an operation."""
@@ -1319,6 +1329,56 @@ def list_generation_rounds(project_id, *, limit=None, offset=0, path=None, conn=
return rounds
def get_successful_generation_history_summary(project_id, path=None, conn=None):
"""Summarize successful effective generation output for one active project."""
try:
project_id = int(project_id)
except (TypeError, ValueError) as exc:
raise db.DbError("商品项目无效") from exc
sql = """
WITH latest_round_jobs AS (
SELECT project_id,
generation_round_key,
generation_slot_index,
MAX(id) AS latest_id
FROM image_studio_jobs
WHERE generation_round_key IS NOT NULL
GROUP BY project_id, generation_round_key, generation_slot_index
),
effective_jobs AS (
SELECT jobs.*
FROM image_studio_jobs AS jobs
LEFT JOIN latest_round_jobs AS latest
ON latest.latest_id = jobs.id
WHERE jobs.generation_round_key IS NULL OR latest.latest_id IS NOT NULL
)
SELECT COUNT(DISTINCT CASE
WHEN jobs.status = 'succeeded'
THEN COALESCE(jobs.generation_round_key, '__legacy__')
END) AS successful_round_count,
SUM(CASE WHEN jobs.status = 'succeeded' THEN 1 ELSE 0 END)
AS successful_image_count,
MAX(CASE
WHEN jobs.status = 'succeeded'
THEN COALESCE(jobs.finished_at, jobs.updated_at, jobs.created_at)
END) AS latest_succeeded_at
FROM image_studio_projects AS projects
LEFT JOIN effective_jobs AS jobs ON jobs.project_id = projects.id
WHERE projects.id = ?
AND projects.deleted_at IS NULL
"""
with _connection(conn, path) as database:
row = database.execute(sql, (project_id,)).fetchone()
return ImageStudioSuccessfulGenerationHistorySummary(
project_id=project_id,
successful_round_count=int(row["successful_round_count"] or 0) if row else 0,
successful_image_count=int(row["successful_image_count"] or 0) if row else 0,
latest_succeeded_at=row["latest_succeeded_at"] if row else None,
)
def list_global_generation_rounds(
*,
account_query="",