feat(ai-studio): add main workspace tab

This commit is contained in:
chengma
2026-07-11 14:16:11 +08:00
parent ab99737e03
commit 5451579073
14 changed files with 1369 additions and 27 deletions
+178
View File
@@ -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 {