feat(ai-studio): add thumbnail workspace grids

This commit is contained in:
chengma
2026-07-11 16:47:18 +08:00
parent 82e7e1bf14
commit e4292f8a0e
3 changed files with 467 additions and 168 deletions
+130 -13
View File
@@ -17,7 +17,7 @@ from app import accounts, ai, appconfig, db, image_paths, image_studio, prompts,
if gui.QT_IMPORT_ERROR is not None:
raise unittest.SkipTest("PySide6 未安装")
from PySide6.QtCore import QItemSelectionModel, QModelIndex, QRect
from PySide6.QtCore import QItemSelectionModel, QModelIndex, QRect, QSize
from PySide6.QtGui import QImage, QKeyEvent, QTextCursor
from PySide6.QtWidgets import QApplication, QComboBox, QLineEdit, QPlainTextEdit, QProgressBar, QTableView
@@ -85,6 +85,31 @@ class FakeGenerateWorker:
FakeGenerateWorker.instances.append(self)
class FakeThumbnailLoader:
def __init__(self):
self.submissions = []
self.cancelled = []
self.closed = False
def submit(self, key, url, on_success=None, on_error=None):
self.submissions.append((key, url, on_success, on_error))
return None
def cancel(self, key=None):
self.cancelled.append(key)
def close(self):
self.closed = True
def succeed(self, index, image_bytes):
_, _, on_success, _ = self.submissions[index]
on_success(SimpleNamespace(image_bytes=image_bytes))
def fail(self, index):
key, _, _, on_error = self.submissions[index]
on_error(key, RuntimeError("缩略图请求失败"))
class GuiTests(TempDirMixin, unittest.TestCase):
@classmethod
def setUpClass(cls):
@@ -496,11 +521,13 @@ class GuiTests(TempDirMixin, unittest.TestCase):
prompts_dir = os.path.join(temp_dir, "prompts", "image_studio")
account = accounts.create_account("主店", "alias-a", debug_port=9222, config=cfg)
prompts.save_image_studio_template("工场模板", "完整提示词", prompts_dir)
thumbnail_loader = FakeThumbnailLoader()
tab = ImageStudioTab(
config=cfg,
db_path=cfg["db_path"],
prompts_dir=prompts_dir,
thumbnail_loader=thumbnail_loader,
)
self.addCleanup(tab.close)
@@ -541,6 +568,36 @@ class GuiTests(TempDirMixin, unittest.TestCase):
tab.template_combo.setCurrentIndex(template_index)
tab.load_selected_template()
self.assertEqual("完整提示词", tab.prompt_edit.toPlainText())
tab.resize(1280, 820)
tab.show()
self.app.processEvents()
self.assertLessEqual(
max(
button.y()
for button in (
tab.template_new_button,
tab.template_rename_button,
tab.template_save_button,
tab.template_delete_button,
)
)
- min(
button.y()
for button in (
tab.template_new_button,
tab.template_rename_button,
tab.template_save_button,
tab.template_delete_button,
)
),
1,
)
self.assertLessEqual(
max(tab.job_type_combo.y(), tab.count_spin.y(), tab.aspect_combo.y())
- min(tab.job_type_combo.y(), tab.count_spin.y(), tab.aspect_combo.y()),
1,
)
self.assertNotIn("当前源图\n单击照片池图片设为源图,双击查看大图", label_texts)
project = image_studio.create_or_get_project(
account,
@@ -570,15 +627,75 @@ class GuiTests(TempDirMixin, unittest.TestCase):
)[0]
tab.refresh_project_assets()
self.assertEqual(1, tab.original_table.rowCount())
self.assertEqual("主图 #1", tab.original_table.item(0, 0).text())
self.assertFalse(tab.original_table.item(0, 0).icon().isNull())
self.assertEqual("远程待下载", tab.original_table.item(0, 1).text())
self.assertEqual(1, tab.pool_table.rowCount())
self.assertEqual(f"原图 #{original.id}", tab.pool_table.item(0, 0).text())
self.assertFalse(tab.pool_table.item(0, 0).icon().isNull())
self.assertEqual("远程待下载", tab.pool_table.item(0, 2).text())
self.assertEqual(original.id, tab.pool_table.item(0, 0).data(gui.Qt.UserRole)["asset_id"])
self.assertEqual(1, tab.original_grid.count())
self.assertIn("主图 1", tab.original_grid.item(0).text())
self.assertIn("加载中", tab.original_grid.item(0).text())
self.assertFalse(tab.original_grid.item(0).icon().isNull())
self.assertEqual(1, len(thumbnail_loader.submissions))
self.assertEqual(0, tab.pool_grid.count())
thumbnail_loader.fail(0)
self.app.processEvents()
self.assertIn("加载失败", tab.original_grid.item(0).text())
self.assertIn("右键重新加载", tab.original_grid.item(0).toolTip())
tab._retry_original_thumbnail(original)
self.assertEqual(2, len(thumbnail_loader.submissions))
self.assertIn("加载中", tab.original_grid.item(0).text())
thumbnail_path = self.write_test_image(os.path.join(temp_dir, "thumbnail.png"))
with open(thumbnail_path, "rb") as image_file:
thumbnail_loader.succeed(1, image_file.read())
self.app.processEvents()
self.assertIn("可预览", tab.original_grid.item(0).text())
self.assertIsNone(image_studio.get_asset(original.id, path=cfg["db_path"]).local_path)
local_original_path = self.write_test_image(os.path.join(temp_dir, "original.png"))
image_studio.update_asset_local_path(original.id, local_original_path, path=cfg["db_path"])
tab.refresh_project_assets()
self.assertEqual(1, tab.pool_grid.count())
pool_item = tab.pool_grid.item(0)
self.assertIn("原图", pool_item.text())
self.assertEqual(original.id, pool_item.data(gui.Qt.UserRole)["asset_id"])
self.assert_removed(temp_dir)
def test_image_studio_thumbnail_grids_keep_order_and_hide_remote_pool_duplicates(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"],
)
image_studio.sync_original_asset_urls(
project.id,
[
{"index": index, "src": f"https://susercontent.com/main-{index}.jpg"}
for index in range(1, 10)
],
path=cfg["db_path"],
)
thumbnail_loader = FakeThumbnailLoader()
tab = ImageStudioTab(
config=cfg,
db_path=cfg["db_path"],
thumbnail_loader=thumbnail_loader,
)
self.addCleanup(tab.close)
tab._select_project(project.id)
self.assertEqual(9, tab.original_grid.count())
self.assertEqual(
[f"主图 {index}" for index in range(1, 10)],
[tab.original_grid.item(row).text().split("\n", 1)[0] for row in range(9)],
)
self.assertEqual(9, len(thumbnail_loader.submissions))
self.assertEqual(0, tab.pool_grid.count())
self.assertEqual(QSize(78, 92), tab.original_grid.gridSize())
self.assertEqual(QSize(108, 124), tab.pool_grid.gridSize())
self.assertGreaterEqual(tab.prompt_edit.minimumHeight(), 210)
self.assert_removed(temp_dir)
@@ -749,9 +866,9 @@ class GuiTests(TempDirMixin, unittest.TestCase):
self.assertEqual("继续查询任务", tab.resume_button.text())
statuses = [
tab.pool_table.item(row, 2).text()
for row in range(tab.pool_table.rowCount())
if tab.pool_table.item(row, 0).text() == "任务"
tab.pool_grid.item(row).text()
for row in range(tab.pool_grid.count())
if tab.pool_grid.item(row).data(gui.Qt.UserRole)["type"] == "job"
]
self.assertEqual(1, len(statuses))
self.assertIn("已提交", statuses[0])