feat(ai-studio): keep workspace usable during generation

This commit is contained in:
chengma
2026-07-13 09:38:30 +08:00
parent 14282306f6
commit 42fe233ed9
4 changed files with 198 additions and 25 deletions
+126
View File
@@ -996,6 +996,132 @@ class GuiTests(TempDirMixin, unittest.TestCase):
self.assert_removed(temp_dir)
def test_image_studio_generation_keeps_current_project_workspace_available(self):
with self.make_temp_dir() as temp_dir:
cfg = self.make_config(temp_dir)
db.init_db(cfg["db_path"])
project = image_studio.create_or_get_project(
account_alias="alias-a",
account_slug="alias_a",
item_id="51100639510",
path=cfg["db_path"],
)
source = image_studio.add_asset(
project.id,
"original",
local_path=self.write_test_image(os.path.join(temp_dir, "source.jpg")),
path=cfg["db_path"],
)
tab = ImageStudioTab(config=cfg, db_path=cfg["db_path"])
self.addCleanup(tab.close)
tab._select_project(project.id)
tab._select_source_asset(source)
tab.prompt_edit.setPlainText("第一轮提示词")
class FakeStudioWorker:
def __init__(self):
self.progress = DummySignal()
self.log = DummySignal()
self.finished = DummySignal()
self.failed = DummySignal()
def cancel(self):
pass
fake_worker = FakeStudioWorker()
fake_thread = FakeThread()
with mock.patch(
"app.gui.tabs.image_studio.ImageStudioGenerateJobsWorker",
return_value=fake_worker,
) as worker_factory, mock.patch(
"app.gui.tabs.image_studio.run_worker", return_value=fake_thread
):
tab.start_generation()
self.assertEqual("generate", tab._operation_kind)
self.assertTrue(tab.pool_grid.isEnabled())
self.assertTrue(tab.main_selection_list.isEnabled())
self.assertTrue(tab.detail_selection_list.isEnabled())
self.assertTrue(tab.template_combo.isEnabled())
self.assertTrue(tab.prompt_edit.isEnabled())
self.assertTrue(tab.job_type_combo.isEnabled())
self.assertTrue(tab.count_spin.isEnabled())
self.assertTrue(tab.aspect_combo.isEnabled())
self.assertTrue(tab.open_folder_button.isEnabled())
self.assertFalse(tab.start_button.isEnabled())
self.assertFalse(tab.resume_button.isEnabled())
self.assertFalse(tab.pull_images_button.isEnabled())
self.assertFalse(tab.project_table.isEnabled())
self.assertFalse(tab.original_grid.isEnabled())
self.assertFalse(tab.export_button.isEnabled())
self.assertFalse(tab.delete_project_button.isEnabled())
self.assertIn("下一轮生成", tab.generation_guard_label.text())
tab.prompt_edit.setPlainText("下一轮提示词")
tab.count_spin.setValue(3)
tab.job_type_combo.setCurrentIndex(tab.job_type_combo.findData("detail"))
tab.aspect_combo.setCurrentIndex(tab.aspect_combo.findData("3:4"))
args = worker_factory.call_args.args
self.assertEqual("第一轮提示词", args[2])
self.assertEqual(4, args[3])
self.assertEqual("main", worker_factory.call_args.kwargs["job_type"])
self.assertEqual("1:1", worker_factory.call_args.kwargs["aspect_ratio"])
tab._finish_worker()
self.assertTrue(tab.start_button.isEnabled())
self.assertEqual("", tab._operation_kind)
self.assertIn("不会在失败后", tab.generation_guard_label.text())
fake_thread.finished.emit()
self.assertIsNone(tab._running_worker)
self.assertIsNone(tab._running_thread)
self.assert_removed(temp_dir)
def test_image_studio_worker_waits_for_thread_cleanup_before_next_start(self):
with self.make_temp_dir() as temp_dir:
cfg = self.make_config(temp_dir)
tab = ImageStudioTab(config=cfg, db_path=cfg["db_path"])
self.addCleanup(tab.close)
first_worker = SimpleNamespace()
second_worker = SimpleNamespace()
first_thread = FakeThread()
second_thread = FakeThread()
with mock.patch(
"app.gui.tabs.image_studio.run_worker",
side_effect=[first_thread, second_thread],
) as run_worker:
self.assertTrue(
tab._start_worker(
first_worker,
"AI工场测试线程",
operation_kind="generate",
)
)
tab._finish_worker()
self.assertFalse(
tab._start_worker(
second_worker,
"AI工场测试线程",
operation_kind="generate",
)
)
self.assertEqual(1, run_worker.call_count)
first_thread.finished.emit()
self.assertTrue(
tab._start_worker(
second_worker,
"AI工场测试线程",
operation_kind="generate",
)
)
self.assertEqual(2, run_worker.call_count)
second_thread.finished.emit()
self.assert_removed(temp_dir)
def test_image_studio_worker_thread_is_kept_until_thread_finished(self):
with self.make_temp_dir() as temp_dir:
cfg = self.make_config(temp_dir)