feat(ai-studio): add main workspace tab
This commit is contained in:
@@ -4,7 +4,9 @@ from __future__ import annotations
|
||||
|
||||
import datetime as _dt
|
||||
import re
|
||||
import threading
|
||||
|
||||
from .. import image_studio, image_studio_generation, image_studio_images
|
||||
from .widgets import *
|
||||
|
||||
|
||||
@@ -14,6 +16,182 @@ _USER_LOG_PATH_RE = re.compile(
|
||||
)
|
||||
|
||||
|
||||
def _image_studio_step_label(step):
|
||||
return {
|
||||
"ensure_chrome": "准备账号浏览器",
|
||||
"login_check": "检测登录",
|
||||
"open_product": "打开商品页",
|
||||
"read_main_images": "读取蝦皮原主图",
|
||||
"cover_submit": "提交生图任务",
|
||||
"cover_poll": "查询生图结果",
|
||||
"cover_download": "下载生成图片",
|
||||
"job_done": "完成单张任务",
|
||||
}.get(str(step or ""), str(step or "处理"))
|
||||
|
||||
|
||||
def _image_studio_result_label(result):
|
||||
return {
|
||||
"start": "开始",
|
||||
"success": "成功",
|
||||
"failed": "失败",
|
||||
"cancelled": "已停止",
|
||||
"blocked": "已阻断",
|
||||
"reused": "已复用",
|
||||
"launched": "已启动",
|
||||
"resume": "继续查询",
|
||||
"queued": "排队中",
|
||||
"running": "生成中",
|
||||
"expired": "已过期",
|
||||
}.get(str(result or ""), str(result or ""))
|
||||
|
||||
|
||||
def _format_image_studio_event(event):
|
||||
event = dict(event or {})
|
||||
step = _image_studio_step_label(event.get("step"))
|
||||
result = _image_studio_result_label(event.get("result"))
|
||||
detail = str(event.get("detail") or "").strip()
|
||||
job_id = event.get("job_id")
|
||||
prefix = f"[AI工场] {step}"
|
||||
if job_id is not None:
|
||||
prefix += f" #{job_id}"
|
||||
if result:
|
||||
prefix += f":{result}"
|
||||
if detail:
|
||||
prefix += f",{diagnostics.redact_log_text(detail)}"
|
||||
if event.get("points_cost") is not None:
|
||||
prefix += f",扣点 {event.get('points_cost')}"
|
||||
if event.get("points_balance") is not None:
|
||||
prefix += f",余额 {event.get('points_balance')}"
|
||||
return prefix
|
||||
|
||||
|
||||
class ImageStudioPullImagesWorker(BaseWorker):
|
||||
"""Read Shopee main image URLs for one AI studio project in background."""
|
||||
|
||||
def __init__(self, account_alias, item_id, *, db_path=None, config=None):
|
||||
super().__init__()
|
||||
self.account_alias = account_alias
|
||||
self.item_id = item_id
|
||||
self.db_path = db_path
|
||||
self.config = config
|
||||
|
||||
def execute(self):
|
||||
def on_step(payload):
|
||||
self.log.emit(_format_image_studio_event(payload))
|
||||
|
||||
result = image_studio.pull_remote_main_image_urls(
|
||||
self.account_alias,
|
||||
self.item_id,
|
||||
path=self.db_path,
|
||||
config=self.config,
|
||||
on_step=on_step,
|
||||
)
|
||||
project = result.get("project")
|
||||
assets = result.get("assets") or []
|
||||
return {
|
||||
"project": project,
|
||||
"assets": assets,
|
||||
"count": len(assets),
|
||||
"account": result.get("account"),
|
||||
}
|
||||
|
||||
|
||||
class ImageStudioDownloadOriginalWorker(BaseWorker):
|
||||
"""Download one remote original image into the project pool."""
|
||||
|
||||
def __init__(self, asset_id, *, db_path=None, config=None, open_after=False):
|
||||
super().__init__()
|
||||
self.asset_id = int(asset_id)
|
||||
self.db_path = db_path
|
||||
self.config = config
|
||||
self.open_after = bool(open_after)
|
||||
|
||||
def execute(self):
|
||||
self.log.emit("[AI工场] 下载原图:开始")
|
||||
asset = image_studio_images.download_original_asset(
|
||||
self.asset_id,
|
||||
path=self.db_path,
|
||||
config=self.config,
|
||||
)
|
||||
self.log.emit("[AI工场] 下载原图:成功")
|
||||
return {"asset": asset, "open_after": self.open_after}
|
||||
|
||||
|
||||
class ImageStudioGenerateJobsWorker(BaseWorker):
|
||||
"""Run cmhub hosted image generation jobs for the AI studio."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
project_id,
|
||||
source_asset_id,
|
||||
prompt,
|
||||
count,
|
||||
*,
|
||||
job_type="main",
|
||||
aspect_ratio="1:1",
|
||||
db_path=None,
|
||||
config=None,
|
||||
cmhub_config_path=None,
|
||||
):
|
||||
super().__init__()
|
||||
self.project_id = int(project_id)
|
||||
self.source_asset_id = int(source_asset_id)
|
||||
self.prompt = str(prompt or "")
|
||||
self.count = int(count or 0)
|
||||
self.job_type = str(job_type or "main")
|
||||
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):
|
||||
self.progress.emit({"total": self.count, "done": 0, "failed": 0})
|
||||
|
||||
def on_event(payload):
|
||||
event = dict(payload or {})
|
||||
self.log.emit(_format_image_studio_event(event))
|
||||
if event.get("points_cost") is not None or event.get("points_balance") is not None:
|
||||
self.progress.emit(
|
||||
{
|
||||
"total": self.count,
|
||||
"done": self._done,
|
||||
"failed": self._failed,
|
||||
"points_cost": event.get("points_cost"),
|
||||
"points_balance": event.get("points_balance"),
|
||||
}
|
||||
)
|
||||
if event.get("step") == "job_done":
|
||||
with self._lock:
|
||||
self._done += 1
|
||||
if event.get("result") not in {"success"}:
|
||||
self._failed += 1
|
||||
progress = {
|
||||
"total": self.count,
|
||||
"done": self._done,
|
||||
"failed": self._failed,
|
||||
}
|
||||
self.progress.emit(progress)
|
||||
|
||||
summary = image_studio_generation.generate_image_jobs(
|
||||
self.project_id,
|
||||
self.source_asset_id,
|
||||
self.prompt,
|
||||
self.count,
|
||||
job_type=self.job_type,
|
||||
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
|
||||
return summary
|
||||
|
||||
|
||||
def _generation_mode_label(mode):
|
||||
mode = appconfig.normalize_generate_mode(mode)
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user