feat(gui): replace AI studio with product suite

This commit is contained in:
chengma
2026-07-14 09:53:13 +08:00
parent fb873aae90
commit bc115ba0d7
27 changed files with 3453 additions and 81 deletions
+157
View File
@@ -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(