feat(product-suite): add cmhub vision AI writing
This commit is contained in:
@@ -54,6 +54,7 @@ from PySide6.QtWidgets import (
|
||||
|
||||
from ... import (
|
||||
accounts,
|
||||
ai,
|
||||
appconfig,
|
||||
diagnostics,
|
||||
image_studio,
|
||||
@@ -3386,8 +3387,9 @@ class ProductSuiteTab(QWidget):
|
||||
return
|
||||
local_assets = [asset for asset in self._original_assets(state) if _asset_usable(asset)]
|
||||
if not local_assets:
|
||||
self._message("缺少商品原图", "请先添加或拉取至少一张可用商品原图。")
|
||||
self._message("缺少可用商品原图", "请先添加商品原图,或等待已拉取的商品原图下载完成。")
|
||||
return
|
||||
selected_assets = local_assets[: ai.CMHUB_VISION_MAX_IMAGES]
|
||||
self._save_controls_to_state(state)
|
||||
context = (
|
||||
"商品ID:%s;平台:%s;国家地区:%s;输出语言:%s。当前已有要求:%s"
|
||||
@@ -3400,15 +3402,15 @@ class ProductSuiteTab(QWidget):
|
||||
)
|
||||
)
|
||||
instruction = (
|
||||
"请为电商商品图片生成简洁、可执行的商品卖点与画面要求。"
|
||||
"只输出可直接用于生图的中文提示词,包含核心卖点、目标人群、使用场景和禁用元素,"
|
||||
"不要虚构商品参数。"
|
||||
"请为当前商品原图生成简洁、可执行的商品卖点与画面要求。"
|
||||
"内容应包含核心卖点、目标人群、使用场景和禁用元素,不要虚构商品参数。"
|
||||
)
|
||||
state.ai_prompt_snapshot = state.prompt
|
||||
state.ai_started_at = time.monotonic()
|
||||
worker = ProductSuiteAiWriteWorker(
|
||||
instruction,
|
||||
context,
|
||||
image_paths=[asset.local_path for asset in selected_assets],
|
||||
config=self.config,
|
||||
cmhub_config_path=self.cmhub_config_path,
|
||||
)
|
||||
@@ -3419,7 +3421,10 @@ class ProductSuiteTab(QWidget):
|
||||
state.ai_thread = self._start_thread(worker, "商品套图AI帮写")
|
||||
if state is self._displayed_state:
|
||||
self._apply_running_state(state)
|
||||
self._status("AI帮写已开始,可切换到其他套图任务", "info")
|
||||
message = "AI帮写已开始,可切换到其他套图任务"
|
||||
if len(local_assets) > ai.CMHUB_VISION_MAX_IMAGES:
|
||||
message += ";已使用前%d张商品原图进行理解" % ai.CMHUB_VISION_MAX_IMAGES
|
||||
self._status(message, "info")
|
||||
|
||||
def cancel_ai_write(self, checked=False):
|
||||
state = self._displayed_state
|
||||
@@ -3444,7 +3449,11 @@ class ProductSuiteTab(QWidget):
|
||||
if state is self._displayed_state:
|
||||
self._offer_pending_ai_result(state)
|
||||
else:
|
||||
self._status("AI帮写已完成,切回对应套图任务后确认是否采用", "success")
|
||||
self._status(
|
||||
self._ai_write_success_message(result)
|
||||
+ ",切回对应套图任务后确认是否采用",
|
||||
"success",
|
||||
)
|
||||
else:
|
||||
state.prompt = text
|
||||
self._persist_state(state)
|
||||
@@ -3454,10 +3463,22 @@ class ProductSuiteTab(QWidget):
|
||||
self.prompt_edit.setPlainText(text)
|
||||
finally:
|
||||
self._loading = False
|
||||
self._status("AI帮写已完成", "success")
|
||||
self._status(self._ai_write_success_message(result), "success")
|
||||
if state is self._displayed_state:
|
||||
self._apply_running_state(state)
|
||||
|
||||
def _ai_write_success_message(self, result):
|
||||
image_count = max(0, int(dict(result or {}).get("image_count", 0) or 0))
|
||||
parts = ["AI帮写完成"]
|
||||
if image_count:
|
||||
parts.append("已理解%d张商品原图" % image_count)
|
||||
metadata = dict(dict(result or {}).get("metadata") or {})
|
||||
if metadata.get("points_cost") is not None:
|
||||
parts.append("图片理解扣点%s" % metadata["points_cost"])
|
||||
if metadata.get("points_balance") is not None:
|
||||
parts.append("当前余额%s" % metadata["points_balance"])
|
||||
return ":".join(parts[:2]) + ("," + ",".join(parts[2:]) if len(parts) > 2 else "")
|
||||
|
||||
def _offer_pending_ai_result(self, state):
|
||||
if not state.pending_ai_text:
|
||||
return
|
||||
|
||||
@@ -88,6 +88,8 @@ class SettingsTab(QWidget):
|
||||
self.cmhub_title_alias_combo.setObjectName("cmhubTitleAliasCombo")
|
||||
self.cmhub_image_alias_combo = QComboBox()
|
||||
self.cmhub_image_alias_combo.setObjectName("cmhubImageAliasCombo")
|
||||
self.cmhub_vision_alias_combo = QComboBox()
|
||||
self.cmhub_vision_alias_combo.setObjectName("cmhubVisionAliasCombo")
|
||||
self.cmhub_connect_timeout_spin = QSpinBox()
|
||||
self.cmhub_connect_timeout_spin.setObjectName("cmhubConnectTimeoutSpin")
|
||||
self.cmhub_connect_timeout_spin.setRange(1, 3600)
|
||||
@@ -304,6 +306,7 @@ class SettingsTab(QWidget):
|
||||
("连接超时(秒)", self.cmhub_connect_timeout_spin),
|
||||
("生文别名", self.cmhub_title_alias_combo),
|
||||
("生图别名", self.cmhub_image_alias_combo),
|
||||
("图片理解别名", self.cmhub_vision_alias_combo),
|
||||
("", self.cmhub_check_balance_checkbox),
|
||||
]
|
||||
)
|
||||
@@ -480,6 +483,7 @@ class SettingsTab(QWidget):
|
||||
self.backend_combo,
|
||||
self.cmhub_title_alias_combo,
|
||||
self.cmhub_image_alias_combo,
|
||||
self.cmhub_vision_alias_combo,
|
||||
self.category_combo,
|
||||
self.api_type_combo,
|
||||
self.default_text_model_combo,
|
||||
@@ -814,6 +818,7 @@ class SettingsTab(QWidget):
|
||||
"base_url": appconfig.normalize_cmhub_base_url(self.cmhub_base_url_edit.text()),
|
||||
"title_alias": self.cmhub_title_alias_combo.currentData() or "",
|
||||
"image_alias": self.cmhub_image_alias_combo.currentData() or "",
|
||||
"vision_alias": self.cmhub_vision_alias_combo.currentData() or "",
|
||||
"connect_timeout": self.cmhub_connect_timeout_spin.value(),
|
||||
"check_balance_before_batch": self.cmhub_check_balance_checkbox.isChecked(),
|
||||
}
|
||||
@@ -866,6 +871,7 @@ class SettingsTab(QWidget):
|
||||
self.cmhub_models,
|
||||
title_selected=cmhub_cfg.get("title_alias", ""),
|
||||
image_selected=cmhub_cfg.get("image_alias", ""),
|
||||
vision_selected=cmhub_cfg.get("vision_alias", ""),
|
||||
)
|
||||
self._set_combo_by_data(
|
||||
self.default_text_model_combo,
|
||||
@@ -1127,6 +1133,7 @@ class SettingsTab(QWidget):
|
||||
self.cmhub_api_key_edit,
|
||||
self.cmhub_title_alias_combo,
|
||||
self.cmhub_image_alias_combo,
|
||||
self.cmhub_vision_alias_combo,
|
||||
self.cmhub_connect_timeout_spin,
|
||||
self.cmhub_check_balance_checkbox,
|
||||
self.cmhub_refresh_button,
|
||||
@@ -1149,17 +1156,27 @@ class SettingsTab(QWidget):
|
||||
current_cfg = appconfig.cmhub_config(self.config)
|
||||
title_selected = self.cmhub_title_alias_combo.currentData() or current_cfg.get("title_alias", "")
|
||||
image_selected = self.cmhub_image_alias_combo.currentData() or current_cfg.get("image_alias", "")
|
||||
vision_selected = self.cmhub_vision_alias_combo.currentData() or current_cfg.get("vision_alias", "")
|
||||
self._populate_cmhub_alias_combos(
|
||||
models,
|
||||
title_selected=title_selected,
|
||||
image_selected=image_selected,
|
||||
vision_selected=vision_selected,
|
||||
)
|
||||
title_count = self._cmhub_alias_count("title")
|
||||
image_count = self._cmhub_alias_count("image")
|
||||
vision_aliases = {
|
||||
str(model.get("alias") or "").strip()
|
||||
for model in self._cmhub_priced_models(models, "vision")
|
||||
}
|
||||
vision_count = len(vision_aliases)
|
||||
balance = payload.get("points_balance")
|
||||
balance_text = f";余额 {balance}" if balance is not None else ""
|
||||
subject = self._cmhub_success_subject(payload)
|
||||
message = f"{subject}:生文别名 {title_count} 个,生图别名 {image_count} 个{balance_text};别名已拉取,记得点『保存设置』持久化"
|
||||
vision_text = f"图片理解别名 {vision_count} 个"
|
||||
if vision_selected and vision_selected not in vision_aliases:
|
||||
vision_text += "(当前已保存值暂不可用)"
|
||||
message = f"{subject}:生文别名 {title_count} 个,生图别名 {image_count} 个,{vision_text}{balance_text};别名已拉取,记得点『保存设置』持久化"
|
||||
self.cmhub_result_label.setText(message)
|
||||
self._set_status(message)
|
||||
|
||||
@@ -1224,10 +1241,21 @@ class SettingsTab(QWidget):
|
||||
self.cmhub_result_label.setText(message)
|
||||
self._set_status(message)
|
||||
|
||||
def _populate_cmhub_alias_combos(self, models, title_selected="", image_selected=""):
|
||||
def _populate_cmhub_alias_combos(
|
||||
self,
|
||||
models,
|
||||
title_selected="",
|
||||
image_selected="",
|
||||
vision_selected="",
|
||||
):
|
||||
if self._suspend_dirty <= 0:
|
||||
with self._dirty_tracking_suspended():
|
||||
self._populate_cmhub_alias_combos(models, title_selected, image_selected)
|
||||
self._populate_cmhub_alias_combos(
|
||||
models,
|
||||
title_selected,
|
||||
image_selected,
|
||||
vision_selected,
|
||||
)
|
||||
return
|
||||
self._populate_cmhub_alias_combo(
|
||||
self.cmhub_title_alias_combo,
|
||||
@@ -1241,6 +1269,12 @@ class SettingsTab(QWidget):
|
||||
"image",
|
||||
image_selected,
|
||||
)
|
||||
self._populate_cmhub_alias_combo(
|
||||
self.cmhub_vision_alias_combo,
|
||||
models,
|
||||
"vision",
|
||||
vision_selected,
|
||||
)
|
||||
|
||||
def _populate_cmhub_alias_combo(self, combo, models, operation, selected):
|
||||
combo.blockSignals(True)
|
||||
@@ -1270,12 +1304,28 @@ class SettingsTab(QWidget):
|
||||
alias = str(model.get("alias") or "").strip()
|
||||
op = str(model.get("operation_type") or "").lower()
|
||||
pricing_status = str(model.get("pricing_status") or "").lower()
|
||||
if alias and op == operation and pricing_status != "unpriced":
|
||||
requires_image = bool(model.get("requires_image"))
|
||||
if (
|
||||
alias
|
||||
and op == operation
|
||||
and pricing_status != "unpriced"
|
||||
and (
|
||||
operation != "vision"
|
||||
or (pricing_status == "priced" and requires_image)
|
||||
)
|
||||
):
|
||||
items.append(model)
|
||||
return items
|
||||
|
||||
def _cmhub_alias_count(self, operation):
|
||||
combo = self.cmhub_title_alias_combo if operation == "title" else self.cmhub_image_alias_combo
|
||||
combos = {
|
||||
"title": self.cmhub_title_alias_combo,
|
||||
"image": self.cmhub_image_alias_combo,
|
||||
"vision": self.cmhub_vision_alias_combo,
|
||||
}
|
||||
combo = combos.get(operation)
|
||||
if combo is None:
|
||||
return 0
|
||||
return sum(1 for index in range(combo.count()) if combo.itemData(index))
|
||||
|
||||
def _show_error(self, error):
|
||||
|
||||
Reference in New Issue
Block a user