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
+51
View File
@@ -867,12 +867,48 @@ class ImageStudioTests(TempDirMixin, unittest.TestCase):
image_studio.ASSET_KIND_ORIGINAL,
path=db_path,
)
other_source = image_studio.add_asset(
other_project.id,
image_studio.ASSET_KIND_ORIGINAL,
path=db_path,
)
other_first = image_studio.create_job(
other_project.id,
source_asset_id=other_source.id,
task_key="other-first-generation-round",
generation_round_key="other-failed-round",
generation_slot_index=0,
path=db_path,
)
image_studio.update_job_status(
other_first.id,
"succeeded",
path=db_path,
)
other_failed = image_studio.create_job(
other_project.id,
source_asset_id=other_source.id,
task_key="other-failed-generation-round",
generation_round_key="other-failed-round",
generation_slot_index=0,
path=db_path,
)
image_studio.update_job_status(
other_failed.id,
"failed",
path=db_path,
)
legacy = image_studio.create_job(
project.id,
source_asset_id=source.id,
task_key="legacy-generation-round",
path=db_path,
)
legacy = image_studio.update_job_status(
legacy.id,
"succeeded",
path=db_path,
)
round_one = "round-one"
first = image_studio.create_job(
project.id,
@@ -1026,6 +1062,21 @@ class ImageStudioTests(TempDirMixin, unittest.TestCase):
self.assertEqual(1, summary.failed_count)
self.assertTrue(rounds[-1].is_legacy)
success_summary = image_studio.get_successful_generation_history_summary(
project.id,
path=db_path,
)
self.assertEqual(project.id, success_summary.project_id)
self.assertEqual(2, success_summary.successful_round_count)
self.assertEqual(3, success_summary.successful_image_count)
self.assertTrue(success_summary.latest_succeeded_at)
other_summary = image_studio.get_successful_generation_history_summary(
other_project.id,
path=db_path,
)
self.assertEqual(0, other_summary.successful_round_count)
self.assertEqual(0, other_summary.successful_image_count)
self.assert_removed(temp_dir)
def test_global_generation_rounds_filter_paginate_and_exclude_deleted_projects(self):
+138
View File
@@ -2118,9 +2118,18 @@ class ProductSuiteGuiTests(TempDirMixin, unittest.TestCase):
self.assertIsInstance(dialog, ProductSuiteGlobalHistoryDialog)
self.assertEqual(project.id, dialog.current_project_id)
self.assertTrue(dialog.isVisible())
self.assertFalse(dialog.current_project_checkbox.isChecked())
tab.open_history_dialog(
current_project_only=True,
current_project_id=project.id,
)
self.app.processEvents()
self.assertTrue(dialog.current_project_checkbox.isChecked())
tab.open_history_dialog()
self.assertIs(dialog, tab._history_dialog)
self.assertFalse(dialog.current_project_checkbox.isChecked())
tab.close_task(0)
self.app.processEvents()
self.assertIs(dialog, tab._history_dialog)
@@ -2924,6 +2933,135 @@ class ProductSuiteGuiTests(TempDirMixin, unittest.TestCase):
self.assert_removed(temp_dir)
def test_successful_history_requires_decision_before_new_generation(self):
with self.make_temp_dir() as temp_dir:
config = self._config(temp_dir)
account = accounts.create_account(
"主店",
"alias-a",
debug_port=9222,
config=config,
)
project = image_studio.create_or_get_project(
account,
item_id="51100639510",
path=config["db_path"],
)
source_path = os.path.join(temp_dir, "source.png")
self._write_image(source_path)
source = image_studio.add_asset(
project.id,
image_studio.ASSET_KIND_ORIGINAL,
local_path=source_path,
path=config["db_path"],
)
history_job = image_studio.create_job(
project.id,
source_asset_id=source.id,
job_type="场景图",
prompt="已有成功历史",
generation_round_key="history-round",
generation_slot_index=0,
path=config["db_path"],
)
image_studio.update_job_status(
history_job.id,
"succeeded",
path=config["db_path"],
)
tab = ProductSuiteTab(config=config, db_path=config["db_path"])
self.addCleanup(tab.close)
state = tab._displayed_state
state.account_alias = "alias-a"
state.item_id = project.item_id
state.project_id = project.id
state.project_binding_state = project.binding_state
state.prompt = "轻便耐用,适合日常使用"
tab._load_state(state)
history_confirmations = []
cost_confirmations = []
tab._confirm_new_generation_history = (
lambda _state, summary: history_confirmations.append(summary) or "cancel"
)
tab._confirm = lambda title, message, **kwargs: cost_confirmations.append(
(title, message, kwargs)
) and False
self.assertFalse(tab.start_generation(state))
self.assertEqual(1, len(history_confirmations))
self.assertEqual(1, history_confirmations[0].successful_round_count)
self.assertEqual(1, history_confirmations[0].successful_image_count)
self.assertEqual([], cost_confirmations)
self.assertEqual(
[history_job.id],
[job.id for job in image_studio.list_jobs(project.id, path=config["db_path"])],
)
tab._confirm_new_generation_history = lambda _state, _summary: "history"
tab.open_history_dialog = mock.Mock()
self.assertFalse(tab.start_generation(state))
tab.open_history_dialog.assert_called_once_with(
current_project_only=True,
current_project_id=project.id,
)
self.assertEqual([], cost_confirmations)
tab._confirm_new_generation_history = lambda _state, _summary: "continue"
tab._confirm = lambda title, message, **kwargs: cost_confirmations.append(
(title, message, kwargs)
) or True
with mock.patch.object(tab, "_start_thread", return_value=mock.Mock()):
self.assertTrue(tab.start_generation(state))
self.assertEqual(1, len(cost_confirmations))
self.assertEqual("确认生成商品套图", cost_confirmations[0][0])
self.assertNotEqual("history-round", state.generation_round_key)
self.assertTrue(state.generation_round_key)
state.worker = None
state.thread = None
state.generation_run_token = ""
tab._generation_run_states.clear()
failed_job = image_studio.create_job(
project.id,
source_asset_id=source.id,
job_type="场景图",
prompt="失败重试",
generation_round_key="retry-round",
generation_slot_index=0,
path=config["db_path"],
)
failed_job = image_studio.update_job_status(
failed_job.id,
"failed",
path=config["db_path"],
)
history_decision = mock.Mock(return_value="cancel")
tab._confirm_new_generation_history = history_decision
tab._confirm = mock.Mock(return_value=True)
with mock.patch.object(tab, "_start_thread", return_value=mock.Mock()):
self.assertTrue(
tab.start_generation(
state,
specs=[
{
"source_asset_id": source.id,
"job_type": failed_job.job_type,
"prompt": failed_job.prompt,
}
],
retry_job_id=failed_job.id,
)
)
history_decision.assert_not_called()
tab._confirm.assert_not_called()
state.worker = None
state.thread = None
state.generation_run_token = ""
tab._generation_run_states.clear()
self.assert_removed(temp_dir)
def test_original_checkbox_click_and_keyboard_delete_keep_actions_separate(self):
original_list = ProductOriginalList()
self.addCleanup(original_list.close)