feat(product-suite): add prompt template settings
This commit is contained in:
@@ -8,14 +8,14 @@ sys.path.insert(0, os.path.dirname(__file__))
|
||||
|
||||
from _helpers import TempDirMixin
|
||||
|
||||
from app import accounts, appconfig, image_studio, image_studio_images
|
||||
from app import accounts, appconfig, image_studio, image_studio_images, product_suite, prompts
|
||||
from app import gui
|
||||
|
||||
if gui.QT_IMPORT_ERROR is not None:
|
||||
raise unittest.SkipTest("PySide6 未安装")
|
||||
|
||||
from PySide6.QtCore import QMimeData, Qt, QUrl
|
||||
from PySide6.QtGui import QIcon, QImage, QPixmap
|
||||
from PySide6.QtGui import QIcon, QImage, QPixmap, QTextCursor
|
||||
from PySide6.QtTest import QTest
|
||||
from PySide6.QtWidgets import QApplication, QLabel, QListWidgetItem, QPushButton
|
||||
|
||||
@@ -26,6 +26,7 @@ from app.gui.tabs.product_suite import (
|
||||
ProductSuiteTab,
|
||||
SuiteResultCard,
|
||||
)
|
||||
from app.gui.product_suite_prompt_dialog import ProductSuitePromptDialog
|
||||
|
||||
|
||||
class ProductSuiteGuiTests(TempDirMixin, unittest.TestCase):
|
||||
@@ -150,6 +151,19 @@ class ProductSuiteGuiTests(TempDirMixin, unittest.TestCase):
|
||||
self.assertEqual("打开结果文件夹", tab.open_folder_button.text())
|
||||
self.assertEqual("合计 5 张", tab.category_total_label.text())
|
||||
self.assertEqual("生成套图(5)", tab.generate_button.text())
|
||||
self.assertLess(
|
||||
tab.prompt_title_layout.indexOf(tab.prompt_title_label),
|
||||
tab.prompt_title_layout.indexOf(tab.ai_write_button),
|
||||
)
|
||||
self.assertLess(
|
||||
tab.prompt_title_layout.indexOf(tab.ai_write_button),
|
||||
tab.prompt_title_layout.indexOf(tab.ai_cancel_button),
|
||||
)
|
||||
self.assertLess(
|
||||
tab.prompt_title_layout.indexOf(tab.ai_cancel_button),
|
||||
tab.prompt_title_layout.indexOf(tab.prompt_settings_button),
|
||||
)
|
||||
self.assertEqual("提示词设置", tab.prompt_settings_button.text())
|
||||
|
||||
visible_text = " ".join(
|
||||
[widget.text() for widget in tab.findChildren(QLabel)]
|
||||
@@ -171,6 +185,139 @@ class ProductSuiteGuiTests(TempDirMixin, unittest.TestCase):
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_prompt_settings_dialog_previews_validates_saves_and_restores(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
config = self._config(temp_dir)
|
||||
prompt_path = appconfig.product_suite_prompt_path(config)
|
||||
prompts.ensure_default_product_suite_prompt(prompt_path)
|
||||
dialog = ProductSuitePromptDialog(
|
||||
prompt_path=prompt_path,
|
||||
base_prompt="40小时续航,适合通勤",
|
||||
settings=product_suite.default_suite_settings(),
|
||||
item_id="51100639510",
|
||||
)
|
||||
self.addCleanup(dialog.close)
|
||||
dialog.show()
|
||||
self.app.processEvents()
|
||||
|
||||
self.assertEqual("白底图", dialog.category_combo.currentData())
|
||||
self.assertTrue(dialog.preview_edit.isReadOnly())
|
||||
self.assertIn("套图名称:白底图,白底主图", dialog.preview_edit.toPlainText())
|
||||
self.assertIn("40小时续航", dialog.preview_edit.toPlainText())
|
||||
sizes = dialog.splitter.sizes()
|
||||
self.assertLessEqual(abs(sizes[0] - sizes[1]), 12)
|
||||
|
||||
cursor = dialog.template_edit.textCursor()
|
||||
cursor.movePosition(QTextCursor.End)
|
||||
dialog.template_edit.setTextCursor(cursor)
|
||||
dialog.insert_variable("商品ID")
|
||||
self.assertTrue(dialog.template_edit.toPlainText().endswith("{商品ID}"))
|
||||
|
||||
default_text = prompts.load_default_product_suite_prompt()
|
||||
invalid_text = default_text.replace("{图片比例}", "")
|
||||
dialog.template_edit.setPlainText(invalid_text)
|
||||
QTest.qWait(230)
|
||||
self.assertFalse(dialog.save_button.isEnabled())
|
||||
self.assertIn("缺少必需变量", dialog.validation_label.text())
|
||||
|
||||
custom_text = "自定义规则\n" + default_text
|
||||
dialog.template_edit.setPlainText(custom_text)
|
||||
QTest.qWait(230)
|
||||
self.assertTrue(dialog.save_button.isEnabled())
|
||||
self.assertTrue(dialog.save_template())
|
||||
self.assertEqual(custom_text, prompts.load_product_suite_prompt(prompt_path))
|
||||
self.assertFalse(dialog.is_dirty())
|
||||
|
||||
dialog.template_edit.setPlainText("临时未保存\n" + custom_text)
|
||||
with mock.patch.object(dialog, "_confirm_restore", return_value=True):
|
||||
self.assertTrue(dialog.restore_default())
|
||||
self.assertEqual(default_text, dialog.template_edit.toPlainText())
|
||||
self.assertEqual(default_text, prompts.load_product_suite_prompt(prompt_path))
|
||||
self.assertFalse(dialog.is_dirty())
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_prompt_settings_dialog_unsaved_close_uses_chinese_three_way_action(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
config = self._config(temp_dir)
|
||||
prompt_path = appconfig.product_suite_prompt_path(config)
|
||||
prompts.ensure_default_product_suite_prompt(prompt_path)
|
||||
dialog = ProductSuitePromptDialog(
|
||||
prompt_path=prompt_path,
|
||||
base_prompt="卖点",
|
||||
settings=product_suite.default_suite_settings(),
|
||||
item_id="",
|
||||
)
|
||||
dialog.show()
|
||||
self.app.processEvents()
|
||||
dialog.template_edit.setPlainText(
|
||||
"未保存修改\n" + prompts.load_default_product_suite_prompt()
|
||||
)
|
||||
self.assertTrue(dialog.is_dirty())
|
||||
|
||||
with mock.patch.object(dialog, "_unsaved_action", return_value="cancel"):
|
||||
dialog.reject()
|
||||
self.assertTrue(dialog.isVisible())
|
||||
with mock.patch.object(dialog, "_unsaved_action", return_value="discard"):
|
||||
dialog.reject()
|
||||
self.assertFalse(dialog.isVisible())
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_invalid_product_suite_template_blocks_before_project_or_worker(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
config = self._config(temp_dir)
|
||||
accounts.create_account("主店", "alias-a", debug_port=9222, config=config)
|
||||
tab = ProductSuiteTab(config=config, db_path=config["db_path"])
|
||||
self.addCleanup(tab.close)
|
||||
with open(tab.product_suite_prompt_path, "w", encoding="utf-8") as handle:
|
||||
handle.write("无效模板{未知变量}")
|
||||
|
||||
message = mock.Mock()
|
||||
with mock.patch.object(tab, "_message", message), mock.patch.object(
|
||||
tab,
|
||||
"_start_thread",
|
||||
) as start_thread:
|
||||
result = tab.start_generation(tab._displayed_state)
|
||||
|
||||
self.assertFalse(result)
|
||||
self.assertIsNone(tab._displayed_state.project_id)
|
||||
self.assertEqual([], image_studio.list_projects(path=config["db_path"]))
|
||||
start_thread.assert_not_called()
|
||||
self.assertEqual("套图提示词模板无效", message.call_args.args[0])
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_prompt_preview_matches_frozen_generation_job_prompt(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
config = self._config(temp_dir)
|
||||
project, _ = self._create_project_with_assets(temp_dir, config, 1)
|
||||
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 = "51100639510"
|
||||
state.project_id = project.id
|
||||
state.prompt = "40小时续航,适合通勤"
|
||||
tab._load_state(state)
|
||||
|
||||
dialog = ProductSuitePromptDialog(
|
||||
prompt_path=tab.product_suite_prompt_path,
|
||||
base_prompt=state.prompt,
|
||||
settings=state.settings,
|
||||
item_id=state.item_id,
|
||||
)
|
||||
self.addCleanup(dialog.close)
|
||||
expected = dialog.preview_edit.toPlainText()
|
||||
|
||||
with mock.patch.object(tab, "_start_thread", return_value=object()):
|
||||
self.assertTrue(tab.start_generation(state))
|
||||
self.assertEqual(expected, state.worker.job_specs[0]["prompt"])
|
||||
state.worker = None
|
||||
state.thread = None
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_category_rows_are_vertical_with_helpers_and_independent_counters(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
config = self._config(temp_dir)
|
||||
|
||||
Reference in New Issue
Block a user