feat(gui): replace AI studio with product suite
This commit is contained in:
@@ -59,6 +59,7 @@ class ImageStudioTests(TempDirMixin, unittest.TestCase):
|
||||
"item_id",
|
||||
"target_main_count",
|
||||
"target_detail_count",
|
||||
"suite_settings_json",
|
||||
"deleted_at",
|
||||
}.issubset(projects_columns)
|
||||
)
|
||||
@@ -72,6 +73,50 @@ class ImageStudioTests(TempDirMixin, unittest.TestCase):
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_init_db_migrates_legacy_projects_with_default_suite_settings(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
db_path = os.path.join(temp_dir, "legacy-project.db")
|
||||
conn = db.connect(db_path)
|
||||
try:
|
||||
conn.execute(
|
||||
"""
|
||||
CREATE TABLE image_studio_projects (
|
||||
id INTEGER PRIMARY KEY,
|
||||
account_alias TEXT NOT NULL,
|
||||
account_slug TEXT NOT NULL,
|
||||
account_name TEXT,
|
||||
item_id TEXT NOT NULL,
|
||||
target_main_count INTEGER NOT NULL DEFAULT 9,
|
||||
target_detail_count INTEGER NOT NULL DEFAULT 12,
|
||||
draft_prompt TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'active',
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL,
|
||||
deleted_at TEXT,
|
||||
deleted_reason TEXT,
|
||||
UNIQUE(account_alias, item_id)
|
||||
)
|
||||
"""
|
||||
)
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO image_studio_projects
|
||||
(id, account_alias, account_slug, item_id, created_at, updated_at)
|
||||
VALUES (1, 'alias', 'alias_slug', '51100639510', '2026-07-14', '2026-07-14')
|
||||
"""
|
||||
)
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
db.init_db(db_path)
|
||||
project = image_studio.get_project(1, path=db_path)
|
||||
|
||||
self.assertEqual("{}", project.suite_settings_json)
|
||||
self.assertEqual({}, image_studio.project_suite_settings(project))
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_project_crud_unique_per_account_and_image_dirs(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
db_path = os.path.join(temp_dir, "cmshopee.db")
|
||||
@@ -110,6 +155,15 @@ class ImageStudioTests(TempDirMixin, unittest.TestCase):
|
||||
|
||||
updated = image_studio.update_project_prompt(project.id, "二次提示词", path=db_path)
|
||||
self.assertEqual("二次提示词", updated.draft_prompt)
|
||||
suite_updated = image_studio.update_project_suite_settings(
|
||||
project.id,
|
||||
{"ratio": "3:4", "categories": {"白底图": 1}},
|
||||
path=db_path,
|
||||
)
|
||||
self.assertEqual(
|
||||
{"ratio": "3:4", "categories": {"白底图": 1}},
|
||||
image_studio.project_suite_settings(suite_updated),
|
||||
)
|
||||
|
||||
dirs = image_studio.project_image_dirs(os.path.join(temp_dir, "images"), project)
|
||||
self.assertEqual(
|
||||
@@ -215,6 +269,43 @@ class ImageStudioTests(TempDirMixin, unittest.TestCase):
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_reorder_original_assets_requires_complete_project_order(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
db_path = os.path.join(temp_dir, "cmshopee.db")
|
||||
db.init_db(db_path)
|
||||
project = image_studio.create_or_get_project(
|
||||
account_alias="alias",
|
||||
account_slug="alias_slug",
|
||||
item_id="51100639510",
|
||||
path=db_path,
|
||||
)
|
||||
first = image_studio.add_asset(
|
||||
project.id,
|
||||
image_studio.ASSET_KIND_ORIGINAL,
|
||||
local_path=os.path.join(temp_dir, "first.png"),
|
||||
source_order=1,
|
||||
path=db_path,
|
||||
)
|
||||
second = image_studio.add_asset(
|
||||
project.id,
|
||||
image_studio.ASSET_KIND_ORIGINAL,
|
||||
local_path=os.path.join(temp_dir, "second.png"),
|
||||
source_order=2,
|
||||
path=db_path,
|
||||
)
|
||||
|
||||
reordered = image_studio.reorder_original_assets(
|
||||
project.id,
|
||||
[second.id, first.id],
|
||||
path=db_path,
|
||||
)
|
||||
|
||||
self.assertEqual([second.id, first.id], [asset.id for asset in reordered])
|
||||
with self.assertRaisesRegex(db.DbError, "全部原图"):
|
||||
image_studio.reorder_original_assets(project.id, [first.id], path=db_path)
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_remove_asset_only_when_not_referenced(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
db_path = os.path.join(temp_dir, "cmshopee.db")
|
||||
@@ -287,6 +378,55 @@ class ImageStudioTests(TempDirMixin, unittest.TestCase):
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_sync_original_asset_urls_preserves_local_upload_and_caps_active_assets(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
db_path = os.path.join(temp_dir, "cmshopee.db")
|
||||
db.init_db(db_path)
|
||||
project = image_studio.create_or_get_project(
|
||||
account_alias="alias",
|
||||
account_slug="alias_slug",
|
||||
item_id="51100639510",
|
||||
path=db_path,
|
||||
)
|
||||
local = image_studio.add_asset(
|
||||
project.id,
|
||||
image_studio.ASSET_KIND_ORIGINAL,
|
||||
local_path=os.path.join(temp_dir, "local.png"),
|
||||
source_order=1,
|
||||
path=db_path,
|
||||
)
|
||||
|
||||
image_studio.sync_original_asset_urls(
|
||||
project.id,
|
||||
[
|
||||
{"index": index, "src": "https://susercontent.com/%d.jpg" % index}
|
||||
for index in range(1, 8)
|
||||
],
|
||||
max_assets=3,
|
||||
path=db_path,
|
||||
)
|
||||
active = image_studio.list_assets(
|
||||
project.id,
|
||||
kind=image_studio.ASSET_KIND_ORIGINAL,
|
||||
include_missing=False,
|
||||
path=db_path,
|
||||
)
|
||||
|
||||
self.assertEqual(3, len(active))
|
||||
self.assertIn(local.id, [asset.id for asset in active])
|
||||
self.assertEqual(
|
||||
image_studio.ASSET_STATUS_AVAILABLE,
|
||||
image_studio.get_asset(local.id, path=db_path).status,
|
||||
)
|
||||
|
||||
image_studio.sync_original_asset_urls(project.id, [], max_assets=3, path=db_path)
|
||||
self.assertEqual(
|
||||
image_studio.ASSET_STATUS_AVAILABLE,
|
||||
image_studio.get_asset(local.id, path=db_path).status,
|
||||
)
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_job_lifecycle_and_resumable_query(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
db_path = os.path.join(temp_dir, "cmshopee.db")
|
||||
@@ -376,6 +516,23 @@ class ImageStudioTests(TempDirMixin, unittest.TestCase):
|
||||
[],
|
||||
image_studio.list_resumable_jobs(path=db_path, include_failed_downloads=True),
|
||||
)
|
||||
self.assertEqual(
|
||||
[terminal.id, job.id],
|
||||
[item.id for item in image_studio.list_jobs(project.id, path=db_path)],
|
||||
)
|
||||
self.assertEqual(
|
||||
[terminal.id],
|
||||
[
|
||||
item.id
|
||||
for item in image_studio.list_jobs(
|
||||
project.id,
|
||||
statuses=["failed"],
|
||||
path=db_path,
|
||||
)
|
||||
],
|
||||
)
|
||||
with self.assertRaisesRegex(db.DbError, "状态无效"):
|
||||
image_studio.list_jobs(project.id, statuses=["unknown"], path=db_path)
|
||||
|
||||
with self.assertRaises(db.DbError):
|
||||
image_studio.create_job(
|
||||
|
||||
Reference in New Issue
Block a user