T-576 增加生成和更新内容模式
This commit is contained in:
@@ -349,9 +349,10 @@ def _has_generated_cover(task):
|
||||
return bool(str(getattr(task, "new_cover_path", "") or "").strip())
|
||||
|
||||
|
||||
def generation_needs(task, generate_cover=False):
|
||||
def generation_needs(task, generate_cover=False, generate_mode=None):
|
||||
"""Return which AI components still need generation for this task."""
|
||||
|
||||
mode = appconfig.normalize_generate_mode(generate_mode, generate_cover=generate_cover)
|
||||
stage = str(getattr(task, "stage", "") or "")
|
||||
status = str(getattr(task, "status", "") or "")
|
||||
if status in {"running", "skipped"}:
|
||||
@@ -364,29 +365,36 @@ def generation_needs(task, generate_cover=False):
|
||||
and _task_attempt_count(task, "apply_attempts") > 0
|
||||
):
|
||||
return {"title": False, "cover": False}
|
||||
needs_title = not _has_generated_title(task)
|
||||
needs_cover = bool(generate_cover) and not _has_generated_cover(task)
|
||||
has_title = _has_generated_title(task)
|
||||
needs_title = appconfig.generate_mode_includes_title(mode) and not has_title
|
||||
needs_cover = False
|
||||
if appconfig.generate_mode_includes_cover(mode) and not _has_generated_cover(task):
|
||||
if mode == "cover":
|
||||
needs_cover = has_title
|
||||
else:
|
||||
needs_cover = has_title or needs_title
|
||||
return {"title": needs_title, "cover": needs_cover}
|
||||
|
||||
|
||||
def is_generatable_task(task, generate_cover=False):
|
||||
def is_generatable_task(task, generate_cover=False, generate_mode=None):
|
||||
"""判断任务是否能由② AI生成执行或重试。"""
|
||||
|
||||
needs = generation_needs(task, generate_cover=generate_cover)
|
||||
needs = generation_needs(task, generate_cover=generate_cover, generate_mode=generate_mode)
|
||||
return bool(needs["title"] or needs["cover"])
|
||||
|
||||
|
||||
def generation_component_totals(tasks, generate_cover=False):
|
||||
def generation_component_totals(tasks, generate_cover=False, generate_mode=None):
|
||||
"""Count task and component gaps for a generation run."""
|
||||
|
||||
mode = appconfig.normalize_generate_mode(generate_mode, generate_cover=generate_cover)
|
||||
eligible = [
|
||||
task for task in list(tasks)
|
||||
if is_generatable_task(task, generate_cover=generate_cover)
|
||||
if is_generatable_task(task, generate_mode=mode)
|
||||
]
|
||||
title_total = 0
|
||||
cover_total = 0
|
||||
for task in eligible:
|
||||
needs = generation_needs(task, generate_cover=generate_cover)
|
||||
needs = generation_needs(task, generate_mode=mode)
|
||||
if needs["title"]:
|
||||
title_total += 1
|
||||
if needs["cover"]:
|
||||
@@ -415,6 +423,7 @@ def generate_batch(tasks, prompts, ai_cfg=None, on_progress=None, should_stop=No
|
||||
"jpg_quality",
|
||||
"resolution",
|
||||
"generate_cover",
|
||||
"generate_mode",
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -428,14 +437,18 @@ def generate_batch(tasks, prompts, ai_cfg=None, on_progress=None, should_stop=No
|
||||
on_error = runtime.get("on_error")
|
||||
title_prompt = _prompt_value(prompts, "title")
|
||||
cover_prompt = _prompt_value(prompts, "cover")
|
||||
generate_cover = bool(generation_cfg.get("generate_cover", False))
|
||||
generate_mode = appconfig.normalize_generate_mode(
|
||||
generation_cfg.get("generate_mode"),
|
||||
generate_cover=generation_cfg.get("generate_cover", False),
|
||||
)
|
||||
generate_cover = appconfig.generate_mode_includes_cover(generate_mode)
|
||||
should_stop = should_stop or (lambda: False)
|
||||
eligible = [
|
||||
task for task in list(tasks)
|
||||
if is_generatable_task(task, generate_cover=generate_cover)
|
||||
if is_generatable_task(task, generate_mode=generate_mode)
|
||||
]
|
||||
needs_by_task = {
|
||||
getattr(task, "id", None): generation_needs(task, generate_cover=generate_cover)
|
||||
getattr(task, "id", None): generation_needs(task, generate_mode=generate_mode)
|
||||
for task in eligible
|
||||
}
|
||||
title_tasks = [
|
||||
@@ -457,6 +470,7 @@ def generate_batch(tasks, prompts, ai_cfg=None, on_progress=None, should_stop=No
|
||||
"failed": 0,
|
||||
"cancelled": False,
|
||||
"generate_cover": generate_cover,
|
||||
"generate_mode": generate_mode,
|
||||
}
|
||||
_emit_generation_progress(on_progress, summary)
|
||||
title_results = {}
|
||||
|
||||
@@ -40,6 +40,8 @@ CMHUB_CONFIG_PATH = os.path.join(default_data_dir(), "config", "cmhub.json")
|
||||
CATEGORIES = {"text", "image"}
|
||||
API_TYPES = {"chat", "images_edits", "auto"}
|
||||
AI_BACKENDS = {"direct", "cmhub"}
|
||||
AI_GENERATE_MODES = {"title", "cover", "title_cover"}
|
||||
SHOPEE_UPDATE_MODES = {"title", "cover", "title_cover"}
|
||||
AI_CONCURRENCY_MIN = 1
|
||||
AI_CONCURRENCY_MAX = 5
|
||||
AI_RETRY_MIN = 0
|
||||
@@ -78,6 +80,7 @@ DEFAULT_CONFIG = {
|
||||
"default_text_model": "GPT-5.5 文本",
|
||||
"default_image_model": "Nano Banana 2",
|
||||
"generate_cover": False,
|
||||
"generate_mode": "",
|
||||
"backend": "cmhub",
|
||||
"cmhub": {
|
||||
"base_url": "",
|
||||
@@ -104,6 +107,7 @@ DEFAULT_CONFIG = {
|
||||
"test_item_id": "51100639510",
|
||||
"allow_real_submit": False,
|
||||
"allow_cover_update": False,
|
||||
"update_mode": "",
|
||||
"max_items_per_run": 1,
|
||||
"close_success_tab": False,
|
||||
"dry_run": False,
|
||||
@@ -357,6 +361,11 @@ def _normalize_config_values(config, migrate_old_cmhub_connect_timeout=False):
|
||||
return config
|
||||
ai = config.get("ai")
|
||||
if isinstance(ai, dict):
|
||||
ai["generate_mode"] = normalize_generate_mode(
|
||||
ai.get("generate_mode"),
|
||||
generate_cover=ai.get("generate_cover", False),
|
||||
)
|
||||
ai["generate_cover"] = generate_mode_includes_cover(ai["generate_mode"])
|
||||
ai["title_concurrency"] = _clamp_int(
|
||||
ai.get("title_concurrency"),
|
||||
AI_CONCURRENCY_MIN,
|
||||
@@ -385,6 +394,13 @@ def _normalize_config_values(config, migrate_old_cmhub_connect_timeout=False):
|
||||
cmhub["download_with_curl"] = _normalize_cmhub_download_with_curl(
|
||||
cmhub.get("download_with_curl", "auto")
|
||||
)
|
||||
update = config.get("shopee_update")
|
||||
if isinstance(update, dict):
|
||||
update["update_mode"] = normalize_update_mode(
|
||||
update.get("update_mode"),
|
||||
allow_cover_update=update.get("allow_cover_update", False),
|
||||
)
|
||||
update["allow_cover_update"] = update_mode_includes_cover(update["update_mode"])
|
||||
return config
|
||||
|
||||
|
||||
@@ -632,6 +648,55 @@ def ai_config(config=None) -> dict:
|
||||
return copy.deepcopy(_config_or_load(config).get("ai", DEFAULT_CONFIG["ai"]))
|
||||
|
||||
|
||||
def normalize_generate_mode(value=None, generate_cover=None) -> str:
|
||||
text = str(value or "").strip().lower()
|
||||
if text in AI_GENERATE_MODES:
|
||||
return text
|
||||
return "title_cover" if bool(generate_cover) else "title"
|
||||
|
||||
|
||||
def generate_mode_includes_title(mode) -> bool:
|
||||
return normalize_generate_mode(mode) in {"title", "title_cover"}
|
||||
|
||||
|
||||
def generate_mode_includes_cover(mode) -> bool:
|
||||
return normalize_generate_mode(mode) in {"cover", "title_cover"}
|
||||
|
||||
|
||||
def ai_generate_mode(config=None) -> str:
|
||||
ai = ai_config(config)
|
||||
return normalize_generate_mode(ai.get("generate_mode"), generate_cover=ai.get("generate_cover", False))
|
||||
|
||||
|
||||
def normalize_update_mode(value=None, allow_cover_update=None) -> str:
|
||||
text = str(value or "").strip().lower()
|
||||
if text in SHOPEE_UPDATE_MODES:
|
||||
return text
|
||||
return "title_cover" if bool(allow_cover_update) else "title"
|
||||
|
||||
|
||||
def update_mode_includes_title(mode) -> bool:
|
||||
return normalize_update_mode(mode) in {"title", "title_cover"}
|
||||
|
||||
|
||||
def update_mode_includes_cover(mode) -> bool:
|
||||
return normalize_update_mode(mode) in {"cover", "title_cover"}
|
||||
|
||||
|
||||
def shopee_update_config(config=None) -> dict:
|
||||
cfg = _config_or_load(config)
|
||||
loaded = cfg.get("shopee_update", {})
|
||||
if not isinstance(loaded, dict):
|
||||
loaded = {}
|
||||
merged = _deep_merge(DEFAULT_CONFIG["shopee_update"], loaded)
|
||||
merged["update_mode"] = normalize_update_mode(
|
||||
merged.get("update_mode"),
|
||||
allow_cover_update=merged.get("allow_cover_update", False),
|
||||
)
|
||||
merged["allow_cover_update"] = update_mode_includes_cover(merged["update_mode"])
|
||||
return merged
|
||||
|
||||
|
||||
|
||||
def ai_backend(config=None) -> str:
|
||||
ai = ai_config(config)
|
||||
|
||||
+9
-1
@@ -1411,7 +1411,7 @@ def _confirm_update_modal(cdp, timeout=3):
|
||||
return last_present or {"present": False, "clicked": False, "reason": "NO_UPDATE_CONFIRM_MODAL"}
|
||||
|
||||
|
||||
def apply_task(account, task, close_success_tab=False, on_step=None, bring_to_front=True) -> dict:
|
||||
def apply_task(account, task, close_success_tab=False, on_step=None, bring_to_front=True, update_mode=None) -> dict:
|
||||
"""Apply generated title/cover to Shopee.
|
||||
|
||||
The caller must perform the batch confirmation before calling this function.
|
||||
@@ -1426,8 +1426,16 @@ def apply_task(account, task, close_success_tab=False, on_step=None, bring_to_fr
|
||||
try:
|
||||
title_result = None
|
||||
cover_result = None
|
||||
mode = appconfig.normalize_update_mode(
|
||||
update_mode,
|
||||
allow_cover_update=bool(_get(task, "new_cover_path")),
|
||||
)
|
||||
new_title = _get(task, "new_title")
|
||||
new_cover_path = _get(task, "new_cover_path")
|
||||
if not appconfig.update_mode_includes_title(mode):
|
||||
new_title = None
|
||||
if not appconfig.update_mode_includes_cover(mode):
|
||||
new_cover_path = None
|
||||
if new_title:
|
||||
current_step = "change_title"
|
||||
_notify_apply_step(on_step, current_step, "start")
|
||||
|
||||
+131
-24
@@ -17,6 +17,11 @@ def WriteBackWorker(*args, **kwargs):
|
||||
class ApplyTab(QWidget):
|
||||
"""Tab 3: list generated tasks and confirm the update scope."""
|
||||
|
||||
UPDATE_MODE_ITEMS = [
|
||||
("只更新标题", "title"),
|
||||
("只更新封面", "cover"),
|
||||
("更新标题和封面", "title_cover"),
|
||||
]
|
||||
STATUS_FILTERS = [
|
||||
("已生成", "generated"),
|
||||
("失败", "failed"),
|
||||
@@ -36,6 +41,7 @@ class ApplyTab(QWidget):
|
||||
):
|
||||
super().__init__(parent)
|
||||
self.config = appconfig.load_config() if config is None else config
|
||||
self.config_path = self.config.get("config_path") or appconfig.CONFIG_PATH
|
||||
self.db_path = _database_path(db_path, self.config)
|
||||
self.status_callback = status_callback
|
||||
self.open_accounts_callback = open_accounts_callback
|
||||
@@ -110,8 +116,20 @@ class ApplyTab(QWidget):
|
||||
self.write_back_button = QPushButton("回写结果到 Excel")
|
||||
self.stop_update_button.setEnabled(False)
|
||||
self.write_back_button.setEnabled(False)
|
||||
self.update_mode_combo = QComboBox()
|
||||
self.update_mode_combo.setObjectName("applyUpdateModeCombo")
|
||||
self.update_mode_combo.setToolTip("选择本轮要更新标题、封面,或同时更新标题和封面")
|
||||
for label, value in self.UPDATE_MODE_ITEMS:
|
||||
self.update_mode_combo.addItem(label, value)
|
||||
self._set_combo_by_data(
|
||||
self.update_mode_combo,
|
||||
self._shopee_update_config().get("update_mode", "title"),
|
||||
)
|
||||
|
||||
action_layout = QHBoxLayout()
|
||||
action_layout.addWidget(QLabel("更新内容"))
|
||||
action_layout.addWidget(self.update_mode_combo)
|
||||
action_layout.addSpacing(16)
|
||||
action_layout.addWidget(self.preview_update_button)
|
||||
action_layout.addWidget(self.start_update_button)
|
||||
action_layout.addWidget(self.stop_update_button)
|
||||
@@ -137,6 +155,7 @@ class ApplyTab(QWidget):
|
||||
self.refresh_button.clicked.connect(self.refresh_tasks)
|
||||
self.preview_update_button.clicked.connect(self.preview_update)
|
||||
self.start_update_button.clicked.connect(self.start_update)
|
||||
self.update_mode_combo.currentIndexChanged.connect(self._on_update_mode_changed)
|
||||
self.stop_update_button.clicked.connect(self.stop_update)
|
||||
self.reset_update_button.clicked.connect(self.reset_apply_status)
|
||||
self.task_table.customContextMenuRequested.connect(self.show_task_context_menu)
|
||||
@@ -227,12 +246,18 @@ class ApplyTab(QWidget):
|
||||
return
|
||||
tasks = [
|
||||
task for task in self.model.tasks
|
||||
if self._is_actionable_task(task)
|
||||
if self._is_update_candidate_task(task)
|
||||
]
|
||||
if not tasks:
|
||||
self._set_status("当前筛选结果没有可更新任务")
|
||||
return
|
||||
update_cfg = self._shopee_update_config()
|
||||
update_mode = self._current_update_mode()
|
||||
content_error = self._update_content_error(tasks, update_mode)
|
||||
if content_error:
|
||||
QMessageBox.warning(self, "更新内容未生成", content_error)
|
||||
self._set_status(content_error.replace("\n", " "))
|
||||
return
|
||||
dry_run = bool(dry_run)
|
||||
safety_error = self._update_safety_error(tasks, dry_run=dry_run)
|
||||
if safety_error:
|
||||
@@ -256,6 +281,7 @@ class ApplyTab(QWidget):
|
||||
config=self.config,
|
||||
close_success_tab=bool(update_cfg.get("close_success_tab", False)),
|
||||
dry_run=dry_run,
|
||||
update_mode=update_mode,
|
||||
parallel_accounts=bool(update_cfg.get("parallel_accounts", False)),
|
||||
max_parallel_accounts=max(
|
||||
1,
|
||||
@@ -368,11 +394,10 @@ class ApplyTab(QWidget):
|
||||
return self.model.task_at(0)
|
||||
return None
|
||||
|
||||
def _is_actionable_task(self, task):
|
||||
def _is_update_candidate_task(self, task):
|
||||
return (
|
||||
getattr(task, "stage", None) == "generated"
|
||||
and getattr(task, "status", None) in {"success", "pending", "failed"}
|
||||
and bool(getattr(task, "new_title", None) or getattr(task, "new_cover_path", None))
|
||||
)
|
||||
|
||||
def _populate_batch_filter(self, batches, selected_batch):
|
||||
@@ -464,7 +489,9 @@ class ApplyTab(QWidget):
|
||||
|
||||
def _confirmation_message(self, tasks, dry_run=False):
|
||||
update_cfg = self._shopee_update_config()
|
||||
cover_text = "允许" if update_cfg.get("allow_cover_update") else "不允许"
|
||||
update_mode = self._current_update_mode()
|
||||
update_mode_text = self._update_mode_label(update_mode)
|
||||
cover_warning = "\n本轮将更新线上封面,请确认封面内容无误。" if appconfig.update_mode_includes_cover(update_mode) else ""
|
||||
close_text = "是" if update_cfg.get("close_success_tab") else "否"
|
||||
batch_size = max(1, int(update_cfg.get("max_items_per_run", 1) or 1))
|
||||
batch_count = (len(tasks) + batch_size - 1) // batch_size if tasks else 0
|
||||
@@ -484,13 +511,15 @@ class ApplyTab(QWidget):
|
||||
+ f"店铺:{self._shop_filter_label()}\n"
|
||||
+ f"商品ID:{self._item_filter_label()}\n"
|
||||
+ f"状态:{self._status_label()}\n"
|
||||
+ f"更新内容:{update_mode_text}\n"
|
||||
+ f"任务数:{len(tasks)}\n"
|
||||
+ f"预计批次:{batch_count}\n\n"
|
||||
+ "安全设置:"
|
||||
+ f"封面更新={cover_text},"
|
||||
+ f"每批最大更新条数={batch_size},"
|
||||
+ f"成功后关闭新页={close_text},"
|
||||
+ f"多账号并行={parallel_text}\n\n"
|
||||
+ cover_warning
|
||||
+ ("\n\n" if cover_warning else "")
|
||||
+ (
|
||||
"检查只写运行日志,不打开蝦皮、不点击「更新」、不改任务状态。"
|
||||
if dry_run
|
||||
@@ -507,17 +536,6 @@ class ApplyTab(QWidget):
|
||||
"设置未开启「允许真实提交线上商品」,已阻止本次更新。\n"
|
||||
"请到⑤设置 > 蝦皮更新安全开启该开关后再开始更新。"
|
||||
)
|
||||
if not update_cfg.get("allow_cover_update", False):
|
||||
cover_tasks = [
|
||||
str(getattr(task, "item_id", ""))
|
||||
for task in tasks
|
||||
if getattr(task, "new_cover_path", None)
|
||||
]
|
||||
if cover_tasks:
|
||||
return (
|
||||
"设置未开启「允许更新封面」,当前任务包含新封面路径,已阻止本次更新。\n"
|
||||
"请到⑤设置 > 蝦皮更新安全开启该开关,或先筛掉含新封面的任务。"
|
||||
)
|
||||
return None
|
||||
|
||||
def _show_update_safety_error(self, message):
|
||||
@@ -534,13 +552,99 @@ class ApplyTab(QWidget):
|
||||
self.open_settings_callback()
|
||||
|
||||
def _shopee_update_config(self):
|
||||
defaults = appconfig.default_config().get("shopee_update", {})
|
||||
loaded = self.config.get("shopee_update", {})
|
||||
if not isinstance(loaded, dict):
|
||||
loaded = {}
|
||||
merged = dict(defaults)
|
||||
merged.update(loaded)
|
||||
return merged
|
||||
return appconfig.shopee_update_config(self.config)
|
||||
|
||||
def _current_update_mode(self):
|
||||
return appconfig.normalize_update_mode(
|
||||
self.update_mode_combo.currentData(),
|
||||
allow_cover_update=False,
|
||||
)
|
||||
|
||||
def _update_mode_label(self, mode):
|
||||
mode = appconfig.normalize_update_mode(mode)
|
||||
return {
|
||||
"title": "只更新标题",
|
||||
"cover": "只更新封面",
|
||||
"title_cover": "更新标题和封面",
|
||||
}.get(mode, "只更新标题")
|
||||
|
||||
def _set_combo_by_data(self, combo, value):
|
||||
index = combo.findData(value)
|
||||
combo.setCurrentIndex(index if index >= 0 else 0)
|
||||
|
||||
def _on_update_mode_changed(self, index=None):
|
||||
previous = self._shopee_update_config().get("update_mode", "title")
|
||||
if self._save_update_mode_setting(show_status=True):
|
||||
return
|
||||
self.update_mode_combo.blockSignals(True)
|
||||
self._set_combo_by_data(self.update_mode_combo, previous)
|
||||
self.update_mode_combo.blockSignals(False)
|
||||
|
||||
def _save_update_mode_setting(self, show_status=True):
|
||||
update_mode = self._current_update_mode()
|
||||
update_cfg = self._shopee_update_config()
|
||||
update_cfg["update_mode"] = update_mode
|
||||
update_cfg["allow_cover_update"] = appconfig.update_mode_includes_cover(update_mode)
|
||||
payload = {
|
||||
key: value
|
||||
for key, value in self.config.items()
|
||||
if key not in {"config_path", "ai_models_path", "cmhub_config_path", "data_dir"}
|
||||
}
|
||||
payload["shopee_update"] = update_cfg
|
||||
try:
|
||||
saved = appconfig.save_config(payload, path=self.config_path)
|
||||
except Exception as exc:
|
||||
self._set_status(f"更新内容设置保存失败:{exc}")
|
||||
return False
|
||||
internal = {
|
||||
key: value
|
||||
for key, value in self.config.items()
|
||||
if key in {"config_path", "ai_models_path", "cmhub_config_path", "data_dir"}
|
||||
}
|
||||
self.config.clear()
|
||||
self.config.update(saved)
|
||||
self.config.update(internal)
|
||||
if self.config_path != appconfig.CONFIG_PATH:
|
||||
self.config["config_path"] = self.config_path
|
||||
if show_status:
|
||||
self._set_status(f"更新内容已设置为:{self._update_mode_label(update_mode)}")
|
||||
return True
|
||||
|
||||
def _update_content_error(self, tasks, update_mode):
|
||||
missing_title = [
|
||||
task for task in tasks
|
||||
if appconfig.update_mode_includes_title(update_mode)
|
||||
and not str(getattr(task, "new_title", "") or "").strip()
|
||||
]
|
||||
missing_cover = [
|
||||
task for task in tasks
|
||||
if appconfig.update_mode_includes_cover(update_mode)
|
||||
and not str(getattr(task, "new_cover_path", "") or "").strip()
|
||||
]
|
||||
if not missing_title and not missing_cover:
|
||||
return None
|
||||
lines = []
|
||||
mode_text = self._update_mode_label(update_mode)
|
||||
if missing_title:
|
||||
lines.append(f"当前筛选结果中有 {len(missing_title)} 条缺少新标题,不能执行“{mode_text}”。")
|
||||
lines.append("请先回到②AI生成选择“只生成标题”或“生成标题和封面”。")
|
||||
lines.append(f"示例商品ID:{self._sample_item_ids(missing_title)}")
|
||||
if missing_cover:
|
||||
if lines:
|
||||
lines.append("")
|
||||
lines.append(f"当前筛选结果中有 {len(missing_cover)} 条缺少新封面,不能执行“{mode_text}”。")
|
||||
lines.append("请先回到②AI生成选择“只生成封面”或“生成标题和封面”。")
|
||||
lines.append(f"示例商品ID:{self._sample_item_ids(missing_cover)}")
|
||||
return "\n".join(lines)
|
||||
|
||||
def _sample_item_ids(self, tasks):
|
||||
values = [
|
||||
str(getattr(task, "item_id", "") or "").strip()
|
||||
for task in tasks[:5]
|
||||
]
|
||||
values = [value for value in values if value]
|
||||
suffix = f" 等 {len(tasks)} 条" if len(tasks) > 5 else ""
|
||||
return "、".join(values) + suffix if values else "无"
|
||||
|
||||
def _set_apply_running(self, running):
|
||||
self.preview_update_button.setEnabled(not running)
|
||||
@@ -552,6 +656,7 @@ class ApplyTab(QWidget):
|
||||
self.shop_filter.setEnabled(not running)
|
||||
self.item_filter.setEnabled(not running)
|
||||
self.status_filter.setEnabled(not running)
|
||||
self.update_mode_combo.setEnabled(not running)
|
||||
self._update_write_back_button()
|
||||
|
||||
def _set_result_write_back_running(self, running):
|
||||
@@ -563,6 +668,7 @@ class ApplyTab(QWidget):
|
||||
self.shop_filter.setEnabled(not running)
|
||||
self.item_filter.setEnabled(not running)
|
||||
self.status_filter.setEnabled(not running)
|
||||
self.update_mode_combo.setEnabled(not running)
|
||||
self.write_back_button.setEnabled(False if running else bool(self._active_batch_ids()))
|
||||
|
||||
def _forget_apply_thread(self, thread):
|
||||
@@ -637,8 +743,9 @@ class ApplyTab(QWidget):
|
||||
def _reset_run_log(self, tasks, dry_run=False, batch_size=1):
|
||||
self.run_log_view.clear()
|
||||
action = "检查" if dry_run else "更新"
|
||||
mode_text = self._update_mode_label(self._current_update_mode())
|
||||
self._append_run_log(
|
||||
f"本轮{action}开始:任务 {len(tasks)} 条,每批 {batch_size} 条"
|
||||
f"本轮{action}开始:任务 {len(tasks)} 条,更新内容:{mode_text},每批 {batch_size} 条"
|
||||
)
|
||||
|
||||
def _load_latest_run_log(self):
|
||||
|
||||
+72
-38
@@ -751,6 +751,7 @@ def _cover_regenerate_config(config):
|
||||
runtime_config = dict(config or {})
|
||||
ai_settings = appconfig.ai_config(runtime_config)
|
||||
ai_settings["generate_cover"] = True
|
||||
ai_settings["generate_mode"] = "cover"
|
||||
runtime_config["ai"] = ai_settings
|
||||
return runtime_config
|
||||
|
||||
@@ -842,6 +843,11 @@ class GenerateTab(QWidget):
|
||||
"cover": "封面",
|
||||
"all": "标题和封面",
|
||||
}
|
||||
GENERATE_MODE_ITEMS = [
|
||||
("只生成标题", "title"),
|
||||
("只生成封面", "cover"),
|
||||
("生成标题和封面", "title_cover"),
|
||||
]
|
||||
TABLE_COLUMN_WEIGHTS = (1.0, 0.5, 1.8375, 1.8375, 0.4125, 0.4125)
|
||||
|
||||
def __init__(
|
||||
@@ -910,11 +916,12 @@ class GenerateTab(QWidget):
|
||||
self.reset_generate_button = QPushButton("重置生成结果")
|
||||
self.reset_generate_button.setObjectName("resetGenerateButton")
|
||||
self.stop_generate_button.setEnabled(False)
|
||||
ai_settings = appconfig.ai_config(self.config)
|
||||
self.generate_cover_checkbox = QCheckBox("生成封面图片(成本较高)")
|
||||
self.generate_cover_checkbox.setObjectName("generateCoverCheckbox")
|
||||
self.generate_cover_checkbox.setToolTip("关闭后只生成标题并保存为可更新,不调用图片模型")
|
||||
self.generate_cover_checkbox.setChecked(bool(ai_settings.get("generate_cover", False)))
|
||||
self.generate_mode_combo = QComboBox()
|
||||
self.generate_mode_combo.setObjectName("generateModeCombo")
|
||||
self.generate_mode_combo.setToolTip("选择本轮生成标题、封面,或同时生成标题和封面")
|
||||
for label, value in self.GENERATE_MODE_ITEMS:
|
||||
self.generate_mode_combo.addItem(label, value)
|
||||
self._set_combo_by_data(self.generate_mode_combo, appconfig.ai_generate_mode(self.config))
|
||||
self.progress_label = QLabel("进度:标题0/0 · 图片0/0 · 失败0")
|
||||
self.title_progress_label = QLabel("标题 0/0")
|
||||
self.title_progress_label.setObjectName("generateTitleProgressLabel")
|
||||
@@ -1007,7 +1014,7 @@ class GenerateTab(QWidget):
|
||||
self.empty_state_button.clicked.connect(self.open_accounts_callback)
|
||||
self.task_table = QTableView()
|
||||
self.model = GenerateTaskTableModel(self.task_table, db_path=self.db_path, status_callback=self._set_status)
|
||||
self.model.set_generate_cover_enabled(self.generate_cover_checkbox.isChecked())
|
||||
self.model.set_generate_cover_enabled(self._current_generate_mode_includes_cover())
|
||||
self.task_table.setModel(self.model)
|
||||
self.task_table.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||
self.task_table.setSelectionMode(QAbstractItemView.ExtendedSelection)
|
||||
@@ -1060,7 +1067,8 @@ class GenerateTab(QWidget):
|
||||
button_layout.addWidget(self.reset_generate_button)
|
||||
|
||||
bottom_layout = QHBoxLayout()
|
||||
bottom_layout.addWidget(self.generate_cover_checkbox)
|
||||
bottom_layout.addWidget(QLabel("生成内容"))
|
||||
bottom_layout.addWidget(self.generate_mode_combo)
|
||||
bottom_layout.addLayout(progress_layout, 1)
|
||||
bottom_layout.addLayout(button_layout)
|
||||
|
||||
@@ -1084,7 +1092,7 @@ class GenerateTab(QWidget):
|
||||
self.delete_cover_template_action.triggered.connect(self.delete_cover_template)
|
||||
self.insert_title_button.clicked.connect(self.insert_title_placeholder)
|
||||
self.preview_prompt_button.clicked.connect(self.preview_cover_prompt)
|
||||
self.generate_cover_checkbox.toggled.connect(self._on_generate_cover_toggled)
|
||||
self.generate_mode_combo.currentIndexChanged.connect(self._on_generate_mode_changed)
|
||||
self.generate_button.clicked.connect(self.start_generate)
|
||||
self.stop_generate_button.clicked.connect(self.stop_generate)
|
||||
self.reset_generate_button.clicked.connect(self.reset_generated_result)
|
||||
@@ -1115,11 +1123,11 @@ class GenerateTab(QWidget):
|
||||
def _show_current_run_log_empty(self):
|
||||
self.run_log_view.setPlainText("本轮日志会在开始运行后显示")
|
||||
|
||||
def _reset_generate_run_log(self, tasks, generate_cover):
|
||||
def _reset_generate_run_log(self, tasks, generate_mode):
|
||||
self.run_log_view.clear()
|
||||
cover_text = "是" if generate_cover else "否"
|
||||
mode_text = self._generate_mode_label(generate_mode)
|
||||
self._append_generate_log(
|
||||
f"本轮AI生成开始:任务 {len(tasks)} 条,生成封面:{cover_text}"
|
||||
f"本轮AI生成开始:任务 {len(tasks)} 条,生成内容:{mode_text}"
|
||||
)
|
||||
|
||||
def _reset_generation_elapsed(self):
|
||||
@@ -1208,16 +1216,17 @@ class GenerateTab(QWidget):
|
||||
self._refresh_elapsed_labels()
|
||||
self._update_elapsed_timer_state()
|
||||
|
||||
def _on_generate_cover_toggled(self, checked):
|
||||
previous = bool(appconfig.ai_config(self.config).get("generate_cover", False))
|
||||
if self._save_generate_cover_setting(show_status=True):
|
||||
self.model.set_generate_cover_enabled(bool(checked))
|
||||
def _on_generate_mode_changed(self, index=None):
|
||||
previous = appconfig.ai_generate_mode(self.config)
|
||||
current = self._current_generate_mode()
|
||||
if self._save_generate_mode_setting(show_status=True):
|
||||
self.model.set_generate_cover_enabled(appconfig.generate_mode_includes_cover(current))
|
||||
self.refresh_tasks()
|
||||
return
|
||||
self.generate_cover_checkbox.blockSignals(True)
|
||||
self.generate_cover_checkbox.setChecked(previous)
|
||||
self.generate_cover_checkbox.blockSignals(False)
|
||||
self.model.set_generate_cover_enabled(previous)
|
||||
self.generate_mode_combo.blockSignals(True)
|
||||
self._set_combo_by_data(self.generate_mode_combo, previous)
|
||||
self.generate_mode_combo.blockSignals(False)
|
||||
self.model.set_generate_cover_enabled(appconfig.generate_mode_includes_cover(previous))
|
||||
|
||||
def _apply_task_table_column_widths(self):
|
||||
if self.model.columnCount() != len(self.TABLE_COLUMN_WEIGHTS):
|
||||
@@ -1237,9 +1246,11 @@ class GenerateTab(QWidget):
|
||||
used += section_width
|
||||
header.resizeSection(column, section_width)
|
||||
|
||||
def _save_generate_cover_setting(self, show_status=True):
|
||||
generate_cover = bool(self.generate_cover_checkbox.isChecked())
|
||||
def _save_generate_mode_setting(self, show_status=True):
|
||||
generate_mode = self._current_generate_mode()
|
||||
generate_cover = appconfig.generate_mode_includes_cover(generate_mode)
|
||||
ai_settings = appconfig.ai_config(self.config)
|
||||
ai_settings["generate_mode"] = generate_mode
|
||||
ai_settings["generate_cover"] = generate_cover
|
||||
payload = {
|
||||
key: value
|
||||
@@ -1250,7 +1261,7 @@ class GenerateTab(QWidget):
|
||||
try:
|
||||
saved = appconfig.save_config(payload, path=self.config_path)
|
||||
except Exception as exc:
|
||||
self._set_status(f"生成封面开关保存失败:{exc}")
|
||||
self._set_status(f"生成内容设置保存失败:{exc}")
|
||||
return False
|
||||
internal = {
|
||||
key: value
|
||||
@@ -1263,10 +1274,30 @@ class GenerateTab(QWidget):
|
||||
if self.config_path != appconfig.CONFIG_PATH:
|
||||
self.config["config_path"] = self.config_path
|
||||
if show_status:
|
||||
mode = "会同时生成封面图片" if generate_cover else "只生成标题,不生成图片"
|
||||
self._set_status(f"AI生成设置已保存:{mode}")
|
||||
self._set_status(f"AI生成设置已保存:{self._generate_mode_label(generate_mode)}")
|
||||
return True
|
||||
|
||||
def _current_generate_mode(self):
|
||||
return appconfig.normalize_generate_mode(
|
||||
self.generate_mode_combo.currentData(),
|
||||
generate_cover=False,
|
||||
)
|
||||
|
||||
def _current_generate_mode_includes_cover(self):
|
||||
return appconfig.generate_mode_includes_cover(self._current_generate_mode())
|
||||
|
||||
def _generate_mode_label(self, mode):
|
||||
mode = appconfig.normalize_generate_mode(mode)
|
||||
return {
|
||||
"title": "只生成标题",
|
||||
"cover": "只生成封面",
|
||||
"title_cover": "生成标题和封面",
|
||||
}.get(mode, "只生成标题")
|
||||
|
||||
def _set_combo_by_data(self, combo, value):
|
||||
index = combo.findData(value)
|
||||
combo.setCurrentIndex(index if index >= 0 else 0)
|
||||
|
||||
def _load_latest_generate_run_log(self):
|
||||
try:
|
||||
logs = db.list_run_logs(limit=1, run_type="generate", path=self.db_path)
|
||||
@@ -1433,21 +1464,23 @@ class GenerateTab(QWidget):
|
||||
if self.generate_thread is not None:
|
||||
self._set_status("AI 生成正在进行...")
|
||||
return
|
||||
if not self._save_generate_cover_setting(show_status=False):
|
||||
if not self._save_generate_mode_setting(show_status=False):
|
||||
return
|
||||
generate_cover = bool(self.generate_cover_checkbox.isChecked())
|
||||
generate_mode = self._current_generate_mode()
|
||||
generate_cover = appconfig.generate_mode_includes_cover(generate_mode)
|
||||
tasks = [
|
||||
task for task in self.model.tasks
|
||||
if ai.is_generatable_task(task, generate_cover=generate_cover)
|
||||
if ai.is_generatable_task(task, generate_mode=generate_mode)
|
||||
]
|
||||
if not tasks:
|
||||
self._set_status(
|
||||
"当前筛选结果没有可生成的缺失内容;请先在①导入采集完成采集,或勾选“生成封面图片(成本较高)”补封面"
|
||||
)
|
||||
if generate_mode == "cover":
|
||||
self._set_status("当前筛选结果没有可生成封面的任务,请先生成标题。")
|
||||
else:
|
||||
self._set_status("当前筛选结果没有可生成的缺失内容;请先在①导入采集完成采集,或调整生成内容")
|
||||
return
|
||||
component_totals = ai.generation_component_totals(
|
||||
tasks,
|
||||
generate_cover=generate_cover,
|
||||
generate_mode=generate_mode,
|
||||
)
|
||||
prompt_values = {
|
||||
"title": self.title_prompt_edit.toPlainText(),
|
||||
@@ -1466,11 +1499,11 @@ class GenerateTab(QWidget):
|
||||
worker.failed.connect(self._on_generate_failed)
|
||||
worker.finished.connect(self._on_generate_finished)
|
||||
worker.cancelled.connect(self._on_generate_cancelled)
|
||||
self._reset_generate_run_log(tasks, generate_cover)
|
||||
self._reset_generate_run_log(tasks, generate_mode)
|
||||
self._reset_cmhub_balance_label()
|
||||
self._start_generation_elapsed(
|
||||
component_totals["title_total"],
|
||||
component_totals["cover_total"] if generate_cover else 0,
|
||||
component_totals["cover_total"],
|
||||
)
|
||||
thread = run_worker(worker, thread_name="GenerateWorker", start=False)
|
||||
thread.finished.connect(lambda: self._forget_generate_thread(thread))
|
||||
@@ -1483,10 +1516,11 @@ class GenerateTab(QWidget):
|
||||
"title_total": component_totals["title_total"],
|
||||
"title_done": 0,
|
||||
"cover_done": 0,
|
||||
"cover_total": component_totals["cover_total"] if generate_cover else 0,
|
||||
"cover_total": component_totals["cover_total"],
|
||||
"generated_done": 0,
|
||||
"failed": 0,
|
||||
"generate_cover": generate_cover,
|
||||
"generate_mode": generate_mode,
|
||||
}
|
||||
)
|
||||
self._set_status(f"开始 AI 生成:{len(tasks)} 条")
|
||||
@@ -1739,7 +1773,7 @@ class GenerateTab(QWidget):
|
||||
self.save_cover_template_as_action.setEnabled(not running)
|
||||
self.rename_cover_template_action.setEnabled(not running)
|
||||
self.delete_cover_template_action.setEnabled(not running)
|
||||
self.generate_cover_checkbox.setEnabled(not running)
|
||||
self.generate_mode_combo.setEnabled(not running)
|
||||
self._sync_cover_gallery_regenerate_guard()
|
||||
|
||||
def _set_cover_gallery_regenerate_running(self, running):
|
||||
@@ -1984,7 +2018,7 @@ class GenerateTab(QWidget):
|
||||
_set_empty_state(self.empty_state_card, self.empty_state_label, self.empty_state_button)
|
||||
self._set_status(f"AI 生成任务读取失败:{exc}")
|
||||
return
|
||||
self.model.set_generate_cover_enabled(self.generate_cover_checkbox.isChecked())
|
||||
self.model.set_generate_cover_enabled(self._current_generate_mode_includes_cover())
|
||||
self.model.set_tasks(filtered_tasks, accounts_rows)
|
||||
self._apply_task_table_column_widths()
|
||||
self.summary_label.setText(
|
||||
@@ -2082,8 +2116,8 @@ class GenerateTab(QWidget):
|
||||
if selected_status in (None, "all"):
|
||||
return True
|
||||
if selected_status == "to_generate":
|
||||
generate_cover = bool(self.generate_cover_checkbox.isChecked())
|
||||
return ai.is_generatable_task(task, generate_cover=generate_cover) and task.status != "failed"
|
||||
generate_mode = self._current_generate_mode()
|
||||
return ai.is_generatable_task(task, generate_mode=generate_mode) and task.status != "failed"
|
||||
if selected_status == "generated":
|
||||
return task.stage == "generated"
|
||||
if selected_status == "applied":
|
||||
|
||||
+13
-13
@@ -208,6 +208,7 @@ class SettingsTab(QWidget):
|
||||
self.allow_real_submit_checkbox.setObjectName("allowRealSubmitCheckbox")
|
||||
self.allow_cover_update_checkbox = QCheckBox("允许更新封面")
|
||||
self.allow_cover_update_checkbox.setObjectName("allowCoverUpdateCheckbox")
|
||||
self.allow_cover_update_checkbox.setVisible(False)
|
||||
self.max_items_per_run_spin = QSpinBox()
|
||||
self.max_items_per_run_spin.setObjectName("maxItemsPerRunSpin")
|
||||
self.max_items_per_run_spin.setRange(1, 9999)
|
||||
@@ -294,7 +295,6 @@ class SettingsTab(QWidget):
|
||||
("每批最大更新条数", self.max_items_per_run_spin),
|
||||
("", self.allow_real_submit_checkbox),
|
||||
("", self.close_success_tab_checkbox),
|
||||
("", self.allow_cover_update_checkbox),
|
||||
("", self.parallel_accounts_group, 2),
|
||||
]
|
||||
)
|
||||
@@ -519,7 +519,6 @@ class SettingsTab(QWidget):
|
||||
self.cmhub_check_balance_checkbox,
|
||||
self.enabled_checkbox,
|
||||
self.allow_real_submit_checkbox,
|
||||
self.allow_cover_update_checkbox,
|
||||
self.close_success_tab_checkbox,
|
||||
self.parallel_accounts_checkbox,
|
||||
)
|
||||
@@ -780,6 +779,14 @@ class SettingsTab(QWidget):
|
||||
for key, value in self.config.items()
|
||||
if key not in {"config_path", "ai_models_path", "cmhub_config_path", "data_dir"}
|
||||
}
|
||||
try:
|
||||
current_update_cfg = appconfig.shopee_update_config(appconfig.load_config(self.config_path))
|
||||
except Exception:
|
||||
current_update_cfg = self._shopee_update_config()
|
||||
update_mode = appconfig.normalize_update_mode(
|
||||
current_update_cfg.get("update_mode"),
|
||||
allow_cover_update=current_update_cfg.get("allow_cover_update", False),
|
||||
)
|
||||
settings.update(
|
||||
{
|
||||
"chrome_path": self.chrome_path_edit.text().strip(),
|
||||
@@ -796,7 +803,8 @@ class SettingsTab(QWidget):
|
||||
"shopee_update": {
|
||||
"test_item_id": str(self._compat_test_item_id or ""),
|
||||
"allow_real_submit": self.allow_real_submit_checkbox.isChecked(),
|
||||
"allow_cover_update": self.allow_cover_update_checkbox.isChecked(),
|
||||
"allow_cover_update": appconfig.update_mode_includes_cover(update_mode),
|
||||
"update_mode": update_mode,
|
||||
"max_items_per_run": self.max_items_per_run_spin.value(),
|
||||
"close_success_tab": self.close_success_tab_checkbox.isChecked(),
|
||||
"dry_run": False,
|
||||
@@ -912,9 +920,7 @@ class SettingsTab(QWidget):
|
||||
self.allow_real_submit_checkbox.setChecked(
|
||||
bool(update_cfg.get("allow_real_submit", False))
|
||||
)
|
||||
self.allow_cover_update_checkbox.setChecked(
|
||||
bool(update_cfg.get("allow_cover_update", False))
|
||||
)
|
||||
self.allow_cover_update_checkbox.setChecked(bool(update_cfg.get("allow_cover_update", False)))
|
||||
self.max_items_per_run_spin.setValue(
|
||||
max(1, int(update_cfg.get("max_items_per_run", 1) or 1))
|
||||
)
|
||||
@@ -931,13 +937,7 @@ class SettingsTab(QWidget):
|
||||
self._on_backend_changed()
|
||||
|
||||
def _shopee_update_config(self):
|
||||
defaults = appconfig.default_config().get("shopee_update", {})
|
||||
loaded = self.config.get("shopee_update", {})
|
||||
if not isinstance(loaded, dict):
|
||||
loaded = {}
|
||||
merged = dict(defaults)
|
||||
merged.update(loaded)
|
||||
return merged
|
||||
return appconfig.shopee_update_config(self.config)
|
||||
|
||||
def _populate_role_model_combos(self):
|
||||
ai_cfg = appconfig.ai_config(self.config)
|
||||
|
||||
+54
-10
@@ -14,6 +14,24 @@ _USER_LOG_PATH_RE = re.compile(
|
||||
)
|
||||
|
||||
|
||||
def _generation_mode_label(mode):
|
||||
mode = appconfig.normalize_generate_mode(mode)
|
||||
return {
|
||||
"title": "只生成标题",
|
||||
"cover": "只生成封面",
|
||||
"title_cover": "生成标题和封面",
|
||||
}.get(mode, "只生成标题")
|
||||
|
||||
|
||||
def _update_mode_label(mode):
|
||||
mode = appconfig.normalize_update_mode(mode)
|
||||
return {
|
||||
"title": "只更新标题",
|
||||
"cover": "只更新封面",
|
||||
"title_cover": "更新标题和封面",
|
||||
}.get(mode, "只更新标题")
|
||||
|
||||
|
||||
class GenerateWorker(BaseWorker):
|
||||
"""Generate titles and covers for eligible collected or failed generation tasks."""
|
||||
|
||||
@@ -53,14 +71,15 @@ class GenerateWorker(BaseWorker):
|
||||
}
|
||||
self._account_by_alias = account_by_alias
|
||||
ai_cfg = appconfig.ai_config(self.config)
|
||||
generate_cover = bool(ai_cfg.get("generate_cover", False))
|
||||
generate_mode = appconfig.ai_generate_mode(self.config)
|
||||
generate_cover = appconfig.generate_mode_includes_cover(generate_mode)
|
||||
eligible = [
|
||||
task for task in self.tasks
|
||||
if ai.is_generatable_task(task, generate_cover=generate_cover)
|
||||
if ai.is_generatable_task(task, generate_mode=generate_mode)
|
||||
]
|
||||
component_totals = ai.generation_component_totals(
|
||||
eligible,
|
||||
generate_cover=generate_cover,
|
||||
generate_mode=generate_mode,
|
||||
)
|
||||
self._eligible_total = len(eligible)
|
||||
self._task_positions = {
|
||||
@@ -69,11 +88,13 @@ class GenerateWorker(BaseWorker):
|
||||
}
|
||||
batch_ids = self._batch_ids(eligible)
|
||||
self._run_id = self._create_run_log(eligible, batch_ids)
|
||||
mode_text = _generation_mode_label(generate_mode)
|
||||
if generate_cover:
|
||||
if appconfig.ai_backend(self.config) == "cmhub":
|
||||
concurrency = ai.cmhub_image_concurrency_plan(ai_cfg)
|
||||
start_message = "[开始] 本轮生成 {total} 条:标题{title_total},图片{cover_total};标题并发{title_concurrency},图片并发{image_concurrency},cmhub实际生图并发{request_concurrency},下载并发{download_concurrency};开始时间 {started_at}".format(
|
||||
start_message = "[开始] 本轮生成 {total} 条:本轮生成内容:{mode_text};标题{title_total},图片{cover_total};标题并发{title_concurrency},图片并发{image_concurrency},cmhub实际生图并发{request_concurrency},下载并发{download_concurrency};开始时间 {started_at}".format(
|
||||
total=len(eligible),
|
||||
mode_text=mode_text,
|
||||
title_total=component_totals["title_total"],
|
||||
cover_total=component_totals["cover_total"],
|
||||
title_concurrency=ai_cfg.get("title_concurrency", 1),
|
||||
@@ -83,8 +104,9 @@ class GenerateWorker(BaseWorker):
|
||||
started_at=self._run_started_at_text,
|
||||
)
|
||||
else:
|
||||
start_message = "[开始] 本轮生成 {total} 条:标题{title_total},图片{cover_total};标题并发{title_concurrency},图片并发{image_concurrency};开始时间 {started_at}".format(
|
||||
start_message = "[开始] 本轮生成 {total} 条:本轮生成内容:{mode_text};标题{title_total},图片{cover_total};标题并发{title_concurrency},图片并发{image_concurrency};开始时间 {started_at}".format(
|
||||
total=len(eligible),
|
||||
mode_text=mode_text,
|
||||
title_total=component_totals["title_total"],
|
||||
cover_total=component_totals["cover_total"],
|
||||
title_concurrency=ai_cfg.get("title_concurrency", 1),
|
||||
@@ -92,8 +114,9 @@ class GenerateWorker(BaseWorker):
|
||||
started_at=self._run_started_at_text,
|
||||
)
|
||||
else:
|
||||
start_message = "[开始] 本轮生成 {total} 条:本轮仅生成标题,不生成图片;标题{title_total};标题并发{title_concurrency};开始时间 {started_at}".format(
|
||||
start_message = "[开始] 本轮生成 {total} 条:本轮生成内容:{mode_text};标题{title_total};标题并发{title_concurrency};开始时间 {started_at}".format(
|
||||
total=len(eligible),
|
||||
mode_text=mode_text,
|
||||
title_total=component_totals["title_total"],
|
||||
title_concurrency=ai_cfg.get("title_concurrency", 1),
|
||||
started_at=self._run_started_at_text,
|
||||
@@ -112,6 +135,7 @@ class GenerateWorker(BaseWorker):
|
||||
"on_event": self._on_generation_event,
|
||||
"on_error": self._on_generation_error,
|
||||
"generate_cover": generate_cover,
|
||||
"generate_mode": generate_mode,
|
||||
},
|
||||
on_progress=self._emit_generate_progress,
|
||||
should_stop=self._should_stop_generation,
|
||||
@@ -130,6 +154,7 @@ class GenerateWorker(BaseWorker):
|
||||
"failed": len(eligible),
|
||||
"cancelled": self.should_cancel(),
|
||||
"generate_cover": generate_cover,
|
||||
"generate_mode": generate_mode,
|
||||
}
|
||||
user_error = self._user_log_detail(error) or "未知错误"
|
||||
self._log_run_event(
|
||||
@@ -550,6 +575,7 @@ class ApplyWorker(BaseWorker):
|
||||
preflight=True,
|
||||
close_success_tab=False,
|
||||
dry_run=False,
|
||||
update_mode=None,
|
||||
parallel_accounts=False,
|
||||
max_parallel_accounts=1,
|
||||
batch_size=None,
|
||||
@@ -562,6 +588,10 @@ class ApplyWorker(BaseWorker):
|
||||
self.preflight = preflight
|
||||
self.close_success_tab = close_success_tab
|
||||
self.dry_run = bool(dry_run)
|
||||
self.update_mode = appconfig.normalize_update_mode(
|
||||
update_mode,
|
||||
allow_cover_update=appconfig.shopee_update_config(config).get("allow_cover_update", False),
|
||||
)
|
||||
self.parallel_accounts = bool(parallel_accounts)
|
||||
self.max_parallel_accounts = max(1, int(max_parallel_accounts or 1))
|
||||
self.batch_size = None if batch_size is None else max(1, int(batch_size or 1))
|
||||
@@ -595,8 +625,9 @@ class ApplyWorker(BaseWorker):
|
||||
}
|
||||
self._run_id = self._create_run_log(eligible, batch_ids)
|
||||
self._log_run_event(
|
||||
"step=start result=start detail=运行开始:{mode},任务{total},每批最多{batch_size},批次{batch_count},{parallel}".format(
|
||||
"step=start result=start detail=运行开始:{mode},更新内容{update_mode},任务{total},每批最多{batch_size},批次{batch_count},{parallel}".format(
|
||||
mode="检查本轮更新" if self.dry_run else "真实更新",
|
||||
update_mode=_update_mode_label(self.update_mode),
|
||||
total=total,
|
||||
batch_size=batch_size,
|
||||
batch_count=len(batches),
|
||||
@@ -662,7 +693,16 @@ class ApplyWorker(BaseWorker):
|
||||
return (
|
||||
getattr(task, "stage", None) == "generated"
|
||||
and getattr(task, "status", None) in {"success", "pending", "failed"}
|
||||
and bool(getattr(task, "new_title", None) or getattr(task, "new_cover_path", None))
|
||||
and (
|
||||
(
|
||||
appconfig.update_mode_includes_title(self.update_mode)
|
||||
and bool(getattr(task, "new_title", None))
|
||||
)
|
||||
or (
|
||||
appconfig.update_mode_includes_cover(self.update_mode)
|
||||
and bool(getattr(task, "new_cover_path", None))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
def _preflight_block(self, eligible, account_rows, account_by_alias):
|
||||
@@ -823,9 +863,9 @@ class ApplyWorker(BaseWorker):
|
||||
)
|
||||
return "skipped"
|
||||
action_parts = []
|
||||
if getattr(task, "new_title", None):
|
||||
if appconfig.update_mode_includes_title(self.update_mode) and getattr(task, "new_title", None):
|
||||
action_parts.append("标题")
|
||||
if getattr(task, "new_cover_path", None):
|
||||
if appconfig.update_mode_includes_cover(self.update_mode) and getattr(task, "new_cover_path", None):
|
||||
action_parts.append("封面")
|
||||
action_text = "+".join(action_parts) or "无变更"
|
||||
self._log_run_event(
|
||||
@@ -899,6 +939,7 @@ class ApplyWorker(BaseWorker):
|
||||
close_success_tab=self.close_success_tab,
|
||||
on_step=on_step,
|
||||
bring_to_front=bring_to_front,
|
||||
update_mode=self.update_mode,
|
||||
)
|
||||
committed = bool(result.get("committed")) and not result.get("error")
|
||||
error = result.get("error")
|
||||
@@ -1027,6 +1068,7 @@ class ApplyWorker(BaseWorker):
|
||||
"dry_run": self.dry_run,
|
||||
"batch_size": self._current_batch_size,
|
||||
"batch_count": self._batch_count,
|
||||
"update_mode": self.update_mode,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1064,6 +1106,7 @@ class ApplyWorker(BaseWorker):
|
||||
"parallel_accounts": self.parallel_accounts,
|
||||
"batch_size": self._current_batch_size,
|
||||
"batch_count": self._batch_count,
|
||||
"update_mode": self.update_mode,
|
||||
"run_id": self._run_id,
|
||||
}
|
||||
if blocked:
|
||||
@@ -1086,6 +1129,7 @@ class ApplyWorker(BaseWorker):
|
||||
"max_parallel_accounts": self.max_parallel_accounts,
|
||||
"batch_size": self._current_batch_size,
|
||||
"batch_count": self._batch_count,
|
||||
"update_mode": self.update_mode,
|
||||
},
|
||||
path=self.db_path,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user