feat(gui): replace AI studio with product suite
This commit is contained in:
+157
-1
@@ -7,7 +7,7 @@ import re
|
||||
import threading
|
||||
import time
|
||||
|
||||
from .. import image_studio, image_studio_export, image_studio_generation, image_studio_images
|
||||
from .. import ai, image_studio, image_studio_export, image_studio_generation, image_studio_images
|
||||
from ..collect_skip import ALIAS_UNMATCHED, LOGIN_REQUIRED, empty_skip_reason_counts
|
||||
from .widgets import *
|
||||
|
||||
@@ -67,6 +67,10 @@ def _format_image_studio_event(event):
|
||||
return prefix
|
||||
|
||||
|
||||
def _format_product_suite_event(event):
|
||||
return _format_image_studio_event(event).replace("[AI工场]", "[商品套图]", 1)
|
||||
|
||||
|
||||
def _image_studio_user_detail(detail):
|
||||
text = diagnostics.redact_log_text(str(detail or "")).replace("\r", " ").replace("\n", " ").strip()
|
||||
text = _USER_LOG_URL_RE.sub("[链接已隐藏]", text)
|
||||
@@ -286,6 +290,158 @@ class ImageStudioGenerateJobsWorker(BaseWorker):
|
||||
return summary
|
||||
|
||||
|
||||
class ProductSuiteGenerateWorker(BaseWorker):
|
||||
"""Create and run independently configured product-suite jobs."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
project_id,
|
||||
job_specs,
|
||||
*,
|
||||
aspect_ratio="1:1",
|
||||
db_path=None,
|
||||
config=None,
|
||||
cmhub_config_path=None,
|
||||
):
|
||||
super().__init__()
|
||||
self.project_id = int(project_id)
|
||||
self.job_specs = [dict(spec) for spec in (job_specs or [])]
|
||||
self.aspect_ratio = str(aspect_ratio or "1:1")
|
||||
self.db_path = db_path
|
||||
self.config = config
|
||||
self.cmhub_config_path = cmhub_config_path
|
||||
self._done = 0
|
||||
self._failed = 0
|
||||
self._lock = threading.Lock()
|
||||
|
||||
def execute(self):
|
||||
total = len(self.job_specs)
|
||||
if total <= 0:
|
||||
raise ValueError("商品套图生成任务不能为空")
|
||||
jobs = []
|
||||
for spec in self.job_specs:
|
||||
if self.should_cancel():
|
||||
break
|
||||
jobs.append(
|
||||
image_studio.create_job(
|
||||
self.project_id,
|
||||
source_asset_id=spec.get("source_asset_id"),
|
||||
job_type=spec.get("job_type") or "套图",
|
||||
prompt=spec.get("prompt") or "",
|
||||
generation_source="cmhub",
|
||||
provider="cmhub",
|
||||
path=self.db_path,
|
||||
)
|
||||
)
|
||||
self.progress.emit(
|
||||
{
|
||||
"total": len(jobs),
|
||||
"done": 0,
|
||||
"failed": 0,
|
||||
"job_ids": [job.id for job in jobs],
|
||||
}
|
||||
)
|
||||
|
||||
def on_event(payload):
|
||||
event = dict(payload or {})
|
||||
self.log.emit(_format_product_suite_event(event))
|
||||
if event.get("step") == "job_done":
|
||||
with self._lock:
|
||||
self._done += 1
|
||||
if event.get("result") != "success":
|
||||
self._failed += 1
|
||||
progress = {
|
||||
"total": len(jobs),
|
||||
"done": self._done,
|
||||
"failed": self._failed,
|
||||
"job_ids": [job.id for job in jobs],
|
||||
}
|
||||
self.progress.emit(progress)
|
||||
|
||||
summary = image_studio_generation.run_jobs(
|
||||
jobs,
|
||||
aspect_ratio=self.aspect_ratio,
|
||||
config=self.config,
|
||||
cmhub_config_path=self.cmhub_config_path,
|
||||
path=self.db_path,
|
||||
should_stop=self.should_cancel,
|
||||
on_event=on_event,
|
||||
)
|
||||
summary["project_id"] = self.project_id
|
||||
summary["job_ids"] = [job.id for job in jobs]
|
||||
return summary
|
||||
|
||||
|
||||
class ProductSuiteAiWriteWorker(BaseWorker):
|
||||
"""Generate product selling-point copy without blocking the suite workspace."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
instruction,
|
||||
context,
|
||||
*,
|
||||
config=None,
|
||||
cmhub_config_path=None,
|
||||
):
|
||||
super().__init__()
|
||||
self.instruction = str(instruction or "")
|
||||
self.context = str(context or "")
|
||||
self.config = config
|
||||
self.cmhub_config_path = cmhub_config_path
|
||||
|
||||
def execute(self):
|
||||
if self.should_cancel():
|
||||
return {"cancelled": True}
|
||||
text = ai.gen_title(
|
||||
self.instruction,
|
||||
self.context,
|
||||
config=self.config,
|
||||
cmhub_config_path=self.cmhub_config_path,
|
||||
)
|
||||
if self.should_cancel():
|
||||
return {"cancelled": True}
|
||||
return {"text": str(text or "").strip()}
|
||||
|
||||
|
||||
class ProductSuiteImportImagesWorker(BaseWorker):
|
||||
"""Validate and copy local product images outside the GUI thread."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
project_id,
|
||||
*,
|
||||
file_paths=None,
|
||||
image_bytes=None,
|
||||
filename_hint="clipboard.png",
|
||||
db_path=None,
|
||||
config=None,
|
||||
):
|
||||
super().__init__()
|
||||
self.project_id = int(project_id)
|
||||
self.file_paths = list(file_paths or [])
|
||||
self.image_bytes = bytes(image_bytes) if image_bytes is not None else None
|
||||
self.filename_hint = str(filename_hint or "clipboard.png")
|
||||
self.db_path = db_path
|
||||
self.config = config
|
||||
|
||||
def execute(self):
|
||||
if self.image_bytes is not None:
|
||||
asset = image_studio_images.import_original_bytes(
|
||||
self.project_id,
|
||||
self.image_bytes,
|
||||
filename_hint=self.filename_hint,
|
||||
path=self.db_path,
|
||||
config=self.config,
|
||||
)
|
||||
return {"assets": [asset], "errors": [], "limit": 16}
|
||||
return image_studio_images.import_original_files(
|
||||
self.project_id,
|
||||
self.file_paths,
|
||||
path=self.db_path,
|
||||
config=self.config,
|
||||
)
|
||||
|
||||
|
||||
class ImageStudioResumeJobsWorker(BaseWorker):
|
||||
"""Resume submitted/running or failed-download AI studio jobs."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user