feat(settings): support gateway source switching
This commit is contained in:
@@ -109,6 +109,9 @@ class MainWindow(QMainWindow):
|
||||
self.tabs.currentChanged.connect(self._on_tab_changed)
|
||||
for title in TAB_TITLES:
|
||||
self.tabs.addTab(self._build_tab(title), title)
|
||||
settings_tab = self._settings_tab()
|
||||
if hasattr(settings_tab, "settingsSaved"):
|
||||
settings_tab.settingsSaved.connect(self._on_settings_saved)
|
||||
self.setCentralWidget(self.tabs)
|
||||
self.show_status("就绪", level="muted")
|
||||
if startup_status:
|
||||
@@ -182,6 +185,14 @@ class MainWindow(QMainWindow):
|
||||
if hasattr(widget, "refresh_tasks"):
|
||||
widget.refresh_tasks()
|
||||
|
||||
def _on_settings_saved(self, backend):
|
||||
for index in range(self.tabs.count()):
|
||||
widget = self.tabs.widget(index)
|
||||
if hasattr(widget, "refresh_gateway_state"):
|
||||
widget.refresh_gateway_state()
|
||||
label = "自定义网关" if str(backend) == "direct" else "默认网关"
|
||||
self.show_status("设置已保存,当前使用%s" % label, level="success")
|
||||
|
||||
def _on_tab_changed(self, index):
|
||||
if self._reverting_tab_change:
|
||||
self._last_tab_index = index
|
||||
|
||||
@@ -1089,7 +1089,7 @@ class GenerateTab(QWidget):
|
||||
self.failed_progress_label = QLabel("失败 0")
|
||||
self.failed_progress_label.setObjectName("generateFailedProgressLabel")
|
||||
self.failed_progress_label.setVisible(False)
|
||||
self.cmhub_balance_label = QLabel("cmhub余额:未获取")
|
||||
self.cmhub_balance_label = QLabel("默认网关余额:未获取")
|
||||
self.cmhub_balance_label.setObjectName("generateCmhubBalanceLabel")
|
||||
self.cmhub_balance_label.setVisible(False)
|
||||
self.title_elapsed_label = QLabel("生标题用时 0 秒")
|
||||
@@ -1760,6 +1760,21 @@ class GenerateTab(QWidget):
|
||||
if not self._save_generate_mode_setting(show_status=False):
|
||||
return
|
||||
generate_mode = self._current_generate_mode()
|
||||
if not self._is_cmhub_backend():
|
||||
try:
|
||||
ai.validate_direct_generation_config(
|
||||
self.config,
|
||||
generate_mode,
|
||||
models_path=(
|
||||
self.config.get("ai_models_path")
|
||||
or appconfig.ai_models_config_path(self.config)
|
||||
),
|
||||
)
|
||||
except Exception as exc:
|
||||
message = "自定义网关配置不完整:%s。请到⑤设置补齐本轮所需模型的地址、模型ID、API Key、接口类型和启用状态。" % str(exc)
|
||||
QMessageBox.warning(self, "无法开始生成", message)
|
||||
self._set_status("自定义网关配置不完整,请到⑤设置补齐", "warning")
|
||||
return
|
||||
generate_cover = appconfig.generate_mode_includes_cover(generate_mode)
|
||||
base_candidates = self._generation_candidates(generate_mode)
|
||||
if not base_candidates:
|
||||
@@ -1837,7 +1852,12 @@ class GenerateTab(QWidget):
|
||||
"generate_mode": generate_mode,
|
||||
}
|
||||
)
|
||||
self._set_status(f"开始 AI 生成:{len(tasks)} 条")
|
||||
if self._is_cmhub_backend():
|
||||
self._set_status(f"开始 AI 生成:{len(tasks)} 条")
|
||||
else:
|
||||
self._set_status(
|
||||
"开始 AI 生成:%d 条;自定义网关(不计点数,费用由服务商收取)" % len(tasks)
|
||||
)
|
||||
thread.start()
|
||||
|
||||
def _generation_candidates(self, generate_mode):
|
||||
@@ -2282,7 +2302,7 @@ class GenerateTab(QWidget):
|
||||
def _reset_cmhub_balance_label(self):
|
||||
self.cmhub_balance_label.setVisible(False)
|
||||
if self._is_cmhub_backend():
|
||||
self.cmhub_balance_label.setText("cmhub余额:生成后刷新")
|
||||
self.cmhub_balance_label.setText("默认网关余额:生成后刷新")
|
||||
else:
|
||||
self.cmhub_balance_label.setText("")
|
||||
|
||||
@@ -2293,9 +2313,14 @@ class GenerateTab(QWidget):
|
||||
balance = payload.get("points_balance") if isinstance(payload, dict) else None
|
||||
if balance is None:
|
||||
if not self.cmhub_balance_label.text():
|
||||
self.cmhub_balance_label.setText("cmhub余额:未获取")
|
||||
self.cmhub_balance_label.setText("默认网关余额:未获取")
|
||||
return
|
||||
self.cmhub_balance_label.setText(f"cmhub余额:{balance}")
|
||||
self.cmhub_balance_label.setText(f"默认网关余额:{balance}")
|
||||
|
||||
def refresh_gateway_state(self):
|
||||
"""Clear source-specific feedback after ⑤ saves a new gateway source."""
|
||||
|
||||
self._reset_cmhub_balance_label()
|
||||
|
||||
def _is_cmhub_backend(self):
|
||||
try:
|
||||
|
||||
@@ -81,6 +81,7 @@ from ..workers import (
|
||||
CMHubModelCatalogWorker,
|
||||
ImageStudioDownloadOriginalWorker,
|
||||
ImageStudioPullImagesWorker,
|
||||
ImageStudioResumeJobsWorker,
|
||||
ProductSuiteAiWriteWorker,
|
||||
ProductSuiteGenerateWorker,
|
||||
ProductSuiteHistoryExportWorker,
|
||||
@@ -723,13 +724,20 @@ class SuiteResultCard(QFrame):
|
||||
status_label.setWordWrap(False)
|
||||
status_label.setToolTip(status_text)
|
||||
footer.addWidget(status_label, 1)
|
||||
self.retry_button = None
|
||||
if status in {"failed", "expired", "cancelled"}:
|
||||
retry_button = QPushButton("重试")
|
||||
retry_button.setMinimumWidth(52)
|
||||
retry_button.clicked.connect(lambda: self.retryRequested.emit(self.job))
|
||||
footer.addWidget(retry_button)
|
||||
self.retry_button = QPushButton("重试")
|
||||
self.retry_button.setMinimumWidth(52)
|
||||
self.retry_button.clicked.connect(lambda: self.retryRequested.emit(self.job))
|
||||
footer.addWidget(self.retry_button)
|
||||
layout.addLayout(footer)
|
||||
|
||||
def set_retry_enabled(self, enabled, tooltip=""):
|
||||
if self.retry_button is None:
|
||||
return
|
||||
self.retry_button.setEnabled(bool(enabled))
|
||||
self.retry_button.setToolTip(str(tooltip or ""))
|
||||
|
||||
|
||||
class SuiteHistoryImageCard(QFrame):
|
||||
"""Read-only image card used by the product-suite history dialog."""
|
||||
@@ -1884,6 +1892,8 @@ class SuiteTaskState:
|
||||
generation_price_worker: object = None
|
||||
generation_price_thread: object = None
|
||||
generation_confirmation_open: bool = False
|
||||
resume_worker: object = None
|
||||
resume_thread: object = None
|
||||
download_queue: list = field(default_factory=list)
|
||||
downloads: dict = field(default_factory=dict)
|
||||
download_tokens: dict = field(default_factory=dict)
|
||||
@@ -1962,6 +1972,10 @@ class ProductSuiteTab(QWidget):
|
||||
if self._prompt_template_init_error:
|
||||
self._status(self._prompt_template_init_error, "danger")
|
||||
|
||||
def showEvent(self, event):
|
||||
super().showEvent(event)
|
||||
self.refresh_gateway_state()
|
||||
|
||||
def _build_ui(self):
|
||||
root = QVBoxLayout(self)
|
||||
root.setContentsMargins(10, 8, 10, 8)
|
||||
@@ -2111,6 +2125,11 @@ class ProductSuiteTab(QWidget):
|
||||
"QPushButton:hover { background: #245fce; }"
|
||||
)
|
||||
layout.addWidget(self.generate_button)
|
||||
self.resume_submitted_button = QPushButton("继续查询已提交图片")
|
||||
self.resume_submitted_button.setObjectName("suiteResumeSubmittedButton")
|
||||
self.resume_submitted_button.setToolTip("继续查询已提交到默认网关的图片,不会重新生成或再次扣点")
|
||||
self.resume_submitted_button.setVisible(False)
|
||||
layout.addWidget(self.resume_submitted_button)
|
||||
self.generate_helper_label = QLabel("建议填写产品名称、核心卖点、目标人群、使用场景与禁用元素")
|
||||
self.generate_helper_label.setWordWrap(True)
|
||||
self.generate_helper_label.setStyleSheet("color: #6b7280;")
|
||||
@@ -2396,6 +2415,7 @@ class ProductSuiteTab(QWidget):
|
||||
self.custom_category_edit.returnPressed.connect(self._commit_custom_category)
|
||||
self.custom_category_edit.editingFinished.connect(self._finish_custom_category_edit)
|
||||
self.generate_button.clicked.connect(self.toggle_generation)
|
||||
self.resume_submitted_button.clicked.connect(self.resume_submitted_jobs)
|
||||
self.history_button.clicked.connect(self.open_history_dialog)
|
||||
self.undo_button.clicked.connect(self.undo_delete)
|
||||
self.more_button.clicked.connect(self._show_more_menu)
|
||||
@@ -4155,7 +4175,105 @@ class ProductSuiteTab(QWidget):
|
||||
if not state.generation_running():
|
||||
self.generate_button.setText("生成套图(%d)" % total)
|
||||
|
||||
def _is_default_gateway(self):
|
||||
try:
|
||||
return appconfig.ai_backend(self.config) == "cmhub"
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def _is_default_gateway_job(job):
|
||||
return (
|
||||
str(getattr(job, "generation_source", "") or "").strip().lower() == "cmhub"
|
||||
and str(getattr(job, "provider", "") or "").strip().lower() == "cmhub"
|
||||
and bool(str(getattr(job, "task_id", "") or "").strip())
|
||||
)
|
||||
|
||||
def _resumable_default_gateway_jobs(self, state):
|
||||
if state is None or state.project_id is None:
|
||||
return []
|
||||
try:
|
||||
jobs = image_studio.list_resumable_jobs(
|
||||
project_id=state.project_id,
|
||||
include_failed_downloads=True,
|
||||
path=self.db_path,
|
||||
)
|
||||
except Exception:
|
||||
return []
|
||||
return [job for job in jobs if self._is_default_gateway_job(job)]
|
||||
|
||||
def refresh_gateway_state(self):
|
||||
state = self._displayed_state
|
||||
if state is not None:
|
||||
self._apply_running_state(state)
|
||||
self._refresh_results(state)
|
||||
|
||||
def _update_resume_submitted_action(self, state):
|
||||
jobs = self._resumable_default_gateway_jobs(state)
|
||||
self.resume_submitted_button.setVisible(bool(jobs))
|
||||
self.resume_submitted_button.setEnabled(
|
||||
bool(jobs)
|
||||
and state.resume_worker is None
|
||||
and not state.generation_running()
|
||||
and state.generation_price_worker is None
|
||||
)
|
||||
|
||||
def resume_submitted_jobs(self, checked=False):
|
||||
state = self._displayed_state
|
||||
if state is None or state.project_id is None:
|
||||
return
|
||||
if state.resume_worker is not None:
|
||||
self._status("正在继续查询已提交图片", "info")
|
||||
return
|
||||
jobs = self._resumable_default_gateway_jobs(state)
|
||||
if not jobs:
|
||||
self._status("当前没有可继续查询的默认网关图片", "info")
|
||||
self._update_resume_submitted_action(state)
|
||||
return
|
||||
worker = ImageStudioResumeJobsWorker(
|
||||
project_id=state.project_id,
|
||||
aspect_ratio=state.settings.get("ratio", "1:1"),
|
||||
db_path=self.db_path,
|
||||
config=self.config,
|
||||
cmhub_config_path=self.cmhub_config_path,
|
||||
)
|
||||
state.resume_worker = worker
|
||||
worker.finished.connect(
|
||||
lambda result, state=state: self._on_resume_submitted_finished(state, result)
|
||||
)
|
||||
worker.cancelled.connect(
|
||||
lambda result, state=state: self._on_resume_submitted_finished(state, result)
|
||||
)
|
||||
state.resume_thread = self._start_thread(worker, "商品套图继续查询")
|
||||
self._apply_running_state(state)
|
||||
self._status("开始继续查询%d张已提交图片,不会重新生成或再次扣点" % len(jobs), "info")
|
||||
|
||||
def _on_resume_submitted_finished(self, state, result):
|
||||
state.resume_worker = None
|
||||
state.resume_thread = None
|
||||
self._refresh_results(state)
|
||||
self._update_resume_submitted_action(state)
|
||||
if result.get("ok") is False:
|
||||
error = str(result.get("error") or "")
|
||||
if "cmhub_not_configured" in error or "缺少" in error:
|
||||
self._message(
|
||||
"继续查询失败",
|
||||
"默认网关配置不可用,请恢复原默认网关配置后继续查询已提交图片。",
|
||||
)
|
||||
else:
|
||||
self._message("继续查询失败", _user_error(error))
|
||||
return
|
||||
self._status("已完成已提交图片查询,请查看生成结果", "success")
|
||||
|
||||
def _require_default_gateway(self, action):
|
||||
if self._is_default_gateway():
|
||||
return True
|
||||
self._message("当前不可用", "%s仅支持默认网关,请到⑤设置切换后再使用。" % action)
|
||||
return False
|
||||
|
||||
def start_ai_write(self, checked=False):
|
||||
if not self._require_default_gateway("商品套图AI帮写"):
|
||||
return
|
||||
state = self._displayed_state
|
||||
if state is None:
|
||||
return
|
||||
@@ -4482,6 +4600,8 @@ class ProductSuiteTab(QWidget):
|
||||
self.start_generation(state)
|
||||
|
||||
def start_generation(self, state, specs=None, *, retry_job_id=None):
|
||||
if not self._require_default_gateway("商品套图生成"):
|
||||
return False
|
||||
if state.generation_running():
|
||||
self._status("当前套图任务仍在生成", "warning")
|
||||
return False
|
||||
@@ -5414,6 +5534,7 @@ class ProductSuiteTab(QWidget):
|
||||
generation_running = state.generation_running()
|
||||
generation_price_pending = state.generation_price_worker is not None
|
||||
pull_running = state.pull_running()
|
||||
default_gateway = self._is_default_gateway()
|
||||
self.pull_button.setText(
|
||||
"正在停止..."
|
||||
if pull_running and state.pull_stop_requested
|
||||
@@ -5439,7 +5560,9 @@ class ProductSuiteTab(QWidget):
|
||||
widget.setEnabled(not generation_running)
|
||||
for row in self.category_rows.values():
|
||||
row.set_controls_enabled(not generation_running)
|
||||
self.generate_button.setEnabled(True)
|
||||
self.generate_button.setEnabled(
|
||||
generation_running or generation_price_pending or default_gateway
|
||||
)
|
||||
if generation_running:
|
||||
self.generate_button.setText(
|
||||
"正在停止..."
|
||||
@@ -5465,8 +5588,17 @@ class ProductSuiteTab(QWidget):
|
||||
)
|
||||
self._refresh_totals(state)
|
||||
ai_running = state.ai_worker is not None or state.ai_price_worker is not None
|
||||
self.ai_write_button.setEnabled(not ai_running and not generation_running)
|
||||
self.ai_write_button.setEnabled(
|
||||
default_gateway and not ai_running and not generation_running
|
||||
)
|
||||
if not default_gateway and not ai_running:
|
||||
self.ai_write_button.setToolTip("商品套图AI帮写仅支持默认网关")
|
||||
self.generate_button.setToolTip("商品套图生成仅支持默认网关")
|
||||
else:
|
||||
self.ai_write_button.setToolTip("")
|
||||
self.generate_button.setToolTip("")
|
||||
self.ai_cancel_button.setVisible(ai_running)
|
||||
self._update_resume_submitted_action(state)
|
||||
self._update_context_actions(state)
|
||||
|
||||
def _refresh_elapsed(self):
|
||||
@@ -5541,6 +5673,8 @@ class ProductSuiteTab(QWidget):
|
||||
card.retryRequested.connect(self.retry_job)
|
||||
card.menuRequested.connect(self._show_job_menu)
|
||||
card.deleteRequested.connect(self.delete_job_asset)
|
||||
if not self._is_default_gateway():
|
||||
card.set_retry_enabled(False, "商品套图重新生成仅支持默认网关")
|
||||
self.result_grid.addWidget(card, index // columns, index % columns)
|
||||
self.result_summary_label.setText("共 %d 张 · 成功 %d 张" % (len(jobs), success))
|
||||
self.undo_button.setVisible(bool(state.undo_records))
|
||||
@@ -5611,6 +5745,8 @@ class ProductSuiteTab(QWidget):
|
||||
ProductSuitePreviewDialog(asset.local_path, "%s预览" % job.job_type, self).exec()
|
||||
|
||||
def retry_job(self, job):
|
||||
if not self._require_default_gateway("商品套图重新生成"):
|
||||
return
|
||||
state = self._displayed_state
|
||||
if state is None:
|
||||
return
|
||||
@@ -5643,6 +5779,9 @@ class ProductSuiteTab(QWidget):
|
||||
copy_action = menu.addAction("复制路径")
|
||||
folder_action = menu.addAction("打开文件夹")
|
||||
retry_action = menu.addAction("重新生成")
|
||||
if not self._is_default_gateway():
|
||||
retry_action.setEnabled(False)
|
||||
retry_action.setToolTip("商品套图重新生成仅支持默认网关")
|
||||
delete_action = menu.addAction("删除")
|
||||
action = menu.exec(global_position)
|
||||
asset = image_studio.get_asset(job.output_asset_id, path=self.db_path) if job.output_asset_id else None
|
||||
|
||||
+89
-24
@@ -5,6 +5,8 @@ from __future__ import annotations
|
||||
from contextlib import contextmanager
|
||||
import os
|
||||
|
||||
from PySide6.QtCore import Signal
|
||||
|
||||
from ... import ai as ai_module
|
||||
from ... import chrome
|
||||
from ... import cmhub_models
|
||||
@@ -14,7 +16,7 @@ from ..workers import CMHubSettingsWorker as _RealCMHubSettingsWorker
|
||||
|
||||
|
||||
PLAINTEXT_CMHUB_API_KEY_WARNING = (
|
||||
"cmhub API Key 会以本地明文保存到 data/config/cmhub.json,仅供本机调用 cmhub 网关使用。"
|
||||
"默认网关 API Key 会以本地明文保存到 data/config/cmhub.json,仅供本机调用默认网关使用。"
|
||||
"该文件已 gitignore,UI 打码显示,日志/导出不记录明文。"
|
||||
)
|
||||
|
||||
@@ -29,7 +31,9 @@ def CMHubSettingsWorker(*args, **kwargs):
|
||||
class SettingsTab(QWidget):
|
||||
"""Tab 5: AI model definitions stored in data/config/ai_models.json."""
|
||||
|
||||
BACKEND_ITEMS = [("直连模型", "direct"), ("cmhub 网关", "cmhub")]
|
||||
settingsSaved = Signal(str)
|
||||
|
||||
BACKEND_ITEMS = [("默认网关", "cmhub"), ("自定义网关", "direct")]
|
||||
CATEGORY_ITEMS = [("文本", "text"), ("图像", "image")]
|
||||
API_TYPE_ITEMS = [("chat", "chat"), ("images_edits", "images_edits"), ("auto", "auto")]
|
||||
RESOLUTION_ITEMS = ["512", "1k", "2k", "4k"]
|
||||
@@ -77,13 +81,37 @@ class SettingsTab(QWidget):
|
||||
for label, value in self.BACKEND_ITEMS:
|
||||
self.backend_combo.addItem(label, value)
|
||||
self.backend_combo.setVisible(False)
|
||||
self.gateway_default_button = QPushButton("默认网关")
|
||||
self.gateway_default_button.setObjectName("gatewayDefaultButton")
|
||||
self.gateway_default_button.setCheckable(True)
|
||||
self.gateway_custom_button = QPushButton("自定义网关")
|
||||
self.gateway_custom_button.setObjectName("gatewayCustomButton")
|
||||
self.gateway_custom_button.setCheckable(True)
|
||||
self.gateway_button_group = QButtonGroup(self)
|
||||
self.gateway_button_group.setExclusive(True)
|
||||
self.gateway_button_group.addButton(self.gateway_default_button)
|
||||
self.gateway_button_group.addButton(self.gateway_custom_button)
|
||||
self.gateway_selector = QWidget()
|
||||
self.gateway_selector.setObjectName("gatewaySourceSelector")
|
||||
gateway_selector_layout = QHBoxLayout(self.gateway_selector)
|
||||
gateway_selector_layout.setContentsMargins(0, 0, 0, 0)
|
||||
gateway_selector_layout.setSpacing(0)
|
||||
gateway_selector_layout.addWidget(self.gateway_default_button)
|
||||
gateway_selector_layout.addWidget(self.gateway_custom_button)
|
||||
self.gateway_selector.setStyleSheet(
|
||||
"QPushButton { min-width: 108px; padding: 6px 12px; border: 1px solid #b8c0ca; "
|
||||
"background: #f6f8fa; color: #57606a; }"
|
||||
"QPushButton:first-child { border-top-left-radius: 4px; border-bottom-left-radius: 4px; }"
|
||||
"QPushButton:last-child { border-left: 0; border-top-right-radius: 4px; border-bottom-right-radius: 4px; }"
|
||||
"QPushButton:checked { background: #0969da; border-color: #0969da; color: white; font-weight: 600; }"
|
||||
)
|
||||
self.cmhub_base_url_edit = QLineEdit()
|
||||
self.cmhub_base_url_edit.setObjectName("cmhubBaseUrlEdit")
|
||||
self.cmhub_base_url_edit.setPlaceholderText("https://host(不要带 /api 或 /api/v1)")
|
||||
self.cmhub_api_key_edit = QLineEdit()
|
||||
self.cmhub_api_key_edit.setObjectName("cmhubApiKeyEdit")
|
||||
self.cmhub_api_key_edit.setEchoMode(QLineEdit.Password)
|
||||
self.cmhub_api_key_edit.setPlaceholderText("从 cmhub 网页端复制 API Key")
|
||||
self.cmhub_api_key_edit.setPlaceholderText("从默认网关网页端复制 API Key")
|
||||
self.cmhub_title_alias_combo = QComboBox()
|
||||
self.cmhub_title_alias_combo.setObjectName("cmhubTitleAliasCombo")
|
||||
self.cmhub_image_alias_combo = QComboBox()
|
||||
@@ -103,10 +131,10 @@ class SettingsTab(QWidget):
|
||||
self.cmhub_result_label = QLabel("")
|
||||
self.cmhub_result_label.setObjectName("cmhubResultLabel")
|
||||
self.cmhub_result_label.setWordWrap(True)
|
||||
self.cmhub_base_url_hint_label = QLabel("Base URL 只填网关根,如 https://host;不要带 /api 或 /api/v1。")
|
||||
self.cmhub_base_url_hint_label = QLabel("Base URL 只填默认网关根,如 https://host;不要带 /api 或 /api/v1。")
|
||||
self.cmhub_base_url_hint_label.setObjectName("cmhubBaseUrlHintLabel")
|
||||
self.cmhub_base_url_hint_label.setWordWrap(True)
|
||||
self.cmhub_key_hint_label = QLabel("API Key 仅在 cmhub 网页端创建时显示一次;复制到此处后会本地明文保存并打码显示。")
|
||||
self.cmhub_key_hint_label = QLabel("API Key 仅在默认网关网页端创建时显示一次;复制到此处后会本地明文保存并打码显示。")
|
||||
self.cmhub_key_hint_label.setObjectName("cmhubKeyHintLabel")
|
||||
self.cmhub_key_hint_label.setWordWrap(True)
|
||||
|
||||
@@ -235,7 +263,7 @@ class SettingsTab(QWidget):
|
||||
("状态", self.enabled_checkbox),
|
||||
("服务商名", self.name_edit),
|
||||
("类别", self.category_combo),
|
||||
("api_type", self.api_type_combo),
|
||||
("接口类型", self.api_type_combo),
|
||||
("模型ID", self.model_id_edit),
|
||||
("连接超时(秒)", self.connect_timeout_spin),
|
||||
("网址", self.url_edit, True),
|
||||
@@ -296,8 +324,15 @@ class SettingsTab(QWidget):
|
||||
model_detail_layout.addLayout(action_layout)
|
||||
model_detail_layout.addWidget(self.test_result_label)
|
||||
|
||||
self.direct_gateway_notice = QLabel("图片理解与商品套图仅支持默认网关。")
|
||||
self.direct_gateway_notice.setObjectName("directGatewayNoticeLabel")
|
||||
self.direct_gateway_notice.setStyleSheet("color: #6b7280;")
|
||||
self.direct_role_panel = QWidget()
|
||||
self.direct_role_panel.setLayout(direct_role_form)
|
||||
direct_role_layout = QVBoxLayout(self.direct_role_panel)
|
||||
direct_role_layout.setContentsMargins(0, 0, 0, 0)
|
||||
direct_role_layout.setSpacing(8)
|
||||
direct_role_layout.addLayout(direct_role_form)
|
||||
direct_role_layout.addWidget(self.direct_gateway_notice)
|
||||
|
||||
cmhub_form = self._three_column_form(
|
||||
[
|
||||
@@ -332,7 +367,7 @@ class SettingsTab(QWidget):
|
||||
self.settings_panel_layout = panel_layout
|
||||
panel_layout.setContentsMargins(13, 18, 13, 18)
|
||||
self.ai_model_section_title = self._section_title(
|
||||
"cmhub 网关",
|
||||
"生成网关",
|
||||
"settingsAiModelSectionTitle",
|
||||
)
|
||||
self.model_detail_section_title = self._section_title(
|
||||
@@ -352,6 +387,8 @@ class SettingsTab(QWidget):
|
||||
"settingsInfrastructureSectionTitle",
|
||||
)
|
||||
panel_layout.addWidget(self.ai_model_section_title)
|
||||
panel_layout.addWidget(self.gateway_selector)
|
||||
panel_layout.addSpacing(8)
|
||||
panel_layout.addWidget(self.model_picker_panel)
|
||||
panel_layout.addSpacing(14)
|
||||
panel_layout.addWidget(self.model_detail_section_title)
|
||||
@@ -394,7 +431,8 @@ class SettingsTab(QWidget):
|
||||
self.delete_model_button.clicked.connect(self.delete_model)
|
||||
self.save_model_button.clicked.connect(self.save_model)
|
||||
self.test_connection_button.clicked.connect(self.test_connection)
|
||||
self.backend_combo.currentIndexChanged.connect(self._on_backend_changed)
|
||||
self.gateway_default_button.toggled.connect(self._on_gateway_source_toggled)
|
||||
self.gateway_custom_button.toggled.connect(self._on_gateway_source_toggled)
|
||||
self.cmhub_refresh_button.clicked.connect(self.refresh_cmhub_models)
|
||||
self.cmhub_test_button.clicked.connect(self.test_cmhub_connection)
|
||||
self.resolution_combo.currentIndexChanged.connect(
|
||||
@@ -480,7 +518,6 @@ class SettingsTab(QWidget):
|
||||
self.chrome_path_edit,
|
||||
)
|
||||
combos = (
|
||||
self.backend_combo,
|
||||
self.cmhub_title_alias_combo,
|
||||
self.cmhub_image_alias_combo,
|
||||
self.cmhub_vision_alias_combo,
|
||||
@@ -516,6 +553,9 @@ class SettingsTab(QWidget):
|
||||
for widget in checkboxes:
|
||||
widget.toggled.connect(self._mark_dirty)
|
||||
|
||||
self.gateway_default_button.toggled.connect(self._mark_dirty)
|
||||
self.gateway_custom_button.toggled.connect(self._mark_dirty)
|
||||
|
||||
def _mark_dirty(self, *args):
|
||||
if self._suspend_dirty > 0:
|
||||
return
|
||||
@@ -576,13 +616,36 @@ class SettingsTab(QWidget):
|
||||
self._cmhub_auto_refresh_done = True
|
||||
self.refresh_cmhub_models()
|
||||
|
||||
def _selected_backend(self):
|
||||
return "direct" if self.gateway_custom_button.isChecked() else "cmhub"
|
||||
|
||||
def _set_selected_backend(self, backend):
|
||||
value = "direct" if str(backend or "").strip().lower() == "direct" else "cmhub"
|
||||
target = self.gateway_custom_button if value == "direct" else self.gateway_default_button
|
||||
for button in (self.gateway_default_button, self.gateway_custom_button):
|
||||
previous = button.blockSignals(True)
|
||||
button.setChecked(button is target)
|
||||
button.blockSignals(previous)
|
||||
previous = self.backend_combo.blockSignals(True)
|
||||
self._set_combo_by_data(self.backend_combo, value)
|
||||
self.backend_combo.blockSignals(previous)
|
||||
|
||||
def _on_gateway_source_toggled(self, checked):
|
||||
if checked:
|
||||
self._on_backend_changed()
|
||||
|
||||
def _on_backend_changed(self, index=None):
|
||||
backend = self._selected_backend()
|
||||
previous = self.backend_combo.blockSignals(True)
|
||||
self._set_combo_by_data(self.backend_combo, backend)
|
||||
self.backend_combo.blockSignals(previous)
|
||||
self.backend_combo.setVisible(False)
|
||||
self.model_picker_panel.setVisible(False)
|
||||
self.model_detail_section_title.setVisible(False)
|
||||
self.model_detail_panel.setVisible(False)
|
||||
self.direct_role_panel.setVisible(False)
|
||||
self.cmhub_panel.setVisible(True)
|
||||
is_direct = backend == "direct"
|
||||
self.model_picker_panel.setVisible(is_direct)
|
||||
self.model_detail_section_title.setVisible(is_direct)
|
||||
self.model_detail_panel.setVisible(is_direct)
|
||||
self.direct_role_panel.setVisible(is_direct)
|
||||
self.cmhub_panel.setVisible(not is_direct)
|
||||
self._set_cmhub_running(self.cmhub_thread is not None)
|
||||
self._update_button_state()
|
||||
|
||||
@@ -733,8 +796,10 @@ class SettingsTab(QWidget):
|
||||
self._replace_config(saved)
|
||||
self._populate_app_settings()
|
||||
self._set_dirty(False)
|
||||
self._set_status("设置已保存")
|
||||
backend_label = "自定义网关" if self._selected_backend() == "direct" else "默认网关"
|
||||
self._set_status("设置已保存,当前使用%s" % backend_label)
|
||||
QMessageBox.information(self, "保存设置", "设置已保存")
|
||||
self.settingsSaved.emit(self._selected_backend())
|
||||
return True
|
||||
|
||||
def _app_settings_values(self):
|
||||
@@ -748,7 +813,7 @@ class SettingsTab(QWidget):
|
||||
self._show_error("默认调试端口必须在调试端口范围内")
|
||||
return None
|
||||
ai_cfg = appconfig.ai_config(self.config)
|
||||
backend = "cmhub"
|
||||
backend = self._selected_backend()
|
||||
text_model = self.default_text_model_combo.currentData() or ai_cfg.get("default_text_model")
|
||||
image_model = self.default_image_model_combo.currentData() or ai_cfg.get("default_image_model")
|
||||
cmhub_cfg = self._cmhub_settings_values(backend)
|
||||
@@ -847,7 +912,7 @@ class SettingsTab(QWidget):
|
||||
return
|
||||
self._populate_role_model_combos()
|
||||
ai_cfg = appconfig.ai_config(self.config)
|
||||
self._set_combo_by_data(self.backend_combo, "cmhub")
|
||||
self._set_selected_backend(ai_cfg.get("backend", "cmhub"))
|
||||
cmhub_cfg = appconfig.cmhub_config(self.config)
|
||||
self.cmhub_base_url_edit.setText(appconfig.normalize_cmhub_base_url(cmhub_cfg.get("base_url", "")))
|
||||
self._loaded_cmhub_api_key = appconfig.get_cmhub_api_key(path=self.cmhub_config_path)
|
||||
@@ -1092,7 +1157,7 @@ class SettingsTab(QWidget):
|
||||
|
||||
def _start_cmhub_worker(self, include_balance):
|
||||
if self.cmhub_thread is not None:
|
||||
self._set_status("cmhub 检测正在进行...")
|
||||
self._set_status("默认网关检测正在进行...")
|
||||
return
|
||||
base_url = appconfig.normalize_cmhub_base_url(self.cmhub_base_url_edit.text())
|
||||
if base_url != self.cmhub_base_url_edit.text().strip():
|
||||
@@ -1104,7 +1169,7 @@ class SettingsTab(QWidget):
|
||||
if not api_key:
|
||||
missing.append("API Key")
|
||||
if missing:
|
||||
self._show_error("cmhub 配置不完整:缺少 " + "、".join(missing))
|
||||
self._show_error("默认网关配置不完整:缺少 " + "、".join(missing))
|
||||
return
|
||||
worker = CMHubSettingsWorker(
|
||||
base_url,
|
||||
@@ -1121,7 +1186,7 @@ class SettingsTab(QWidget):
|
||||
self.cmhub_worker = worker
|
||||
self.cmhub_thread = thread
|
||||
self._set_cmhub_running(True)
|
||||
message = "正在测试 cmhub 连接并查询余额..." if include_balance else "正在刷新 cmhub 别名..."
|
||||
message = "正在测试默认网关连接并查询余额..." if include_balance else "正在刷新默认网关别名..."
|
||||
self.cmhub_result_label.setText(message)
|
||||
self._set_status(message)
|
||||
thread.start()
|
||||
@@ -1183,8 +1248,8 @@ class SettingsTab(QWidget):
|
||||
def _cmhub_success_subject(self, payload):
|
||||
account_name = self._cmhub_account_display_name(payload)
|
||||
if account_name:
|
||||
return f"cmhub 账号「{account_name}」连接成功"
|
||||
return "cmhub 连接成功"
|
||||
return f"账号「{account_name}」连接默认网关成功"
|
||||
return "默认网关连接成功"
|
||||
|
||||
def _cmhub_account_display_name(self, payload):
|
||||
if not isinstance(payload, dict):
|
||||
@@ -1237,7 +1302,7 @@ class SettingsTab(QWidget):
|
||||
return ""
|
||||
|
||||
def _on_cmhub_failed(self, _task_id, error):
|
||||
message = f"cmhub 连接失败:{error}"
|
||||
message = "默认网关连接失败:%s" % str(error or "连接失败").replace("cmhub", "默认网关")
|
||||
self.cmhub_result_label.setText(message)
|
||||
self._set_status(message)
|
||||
|
||||
|
||||
+39
-5
@@ -285,7 +285,13 @@ class ImageStudioGenerateJobsWorker(BaseWorker):
|
||||
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.config = ai.freeze_runtime_config(
|
||||
config,
|
||||
cmhub_config_path=cmhub_config_path,
|
||||
models_path=(config or {}).get("ai_models_path", appconfig.AI_MODELS_PATH),
|
||||
include_cmhub=True,
|
||||
include_direct_models=False,
|
||||
)
|
||||
self.cmhub_config_path = cmhub_config_path
|
||||
self._done = 0
|
||||
self._failed = 0
|
||||
@@ -358,7 +364,13 @@ class ProductSuiteGenerateWorker(BaseWorker):
|
||||
self.generation_round_key = str(generation_round_key or "").strip()
|
||||
self.aspect_ratio = str(aspect_ratio or "1:1")
|
||||
self.db_path = db_path
|
||||
self.config = config
|
||||
self.config = ai.freeze_runtime_config(
|
||||
config,
|
||||
cmhub_config_path=cmhub_config_path,
|
||||
models_path=(config or {}).get("ai_models_path", appconfig.AI_MODELS_PATH),
|
||||
include_cmhub=True,
|
||||
include_direct_models=False,
|
||||
)
|
||||
self.cmhub_config_path = cmhub_config_path
|
||||
self.job_ids = []
|
||||
self._done = 0
|
||||
@@ -366,6 +378,8 @@ class ProductSuiteGenerateWorker(BaseWorker):
|
||||
self._lock = threading.Lock()
|
||||
|
||||
def execute(self):
|
||||
if appconfig.ai_backend(self.config) != "cmhub":
|
||||
raise ValueError("商品套图仅支持默认网关,请到⑤设置切换后再生成")
|
||||
total = len(self.job_specs)
|
||||
if total <= 0:
|
||||
raise ValueError("商品套图生成任务不能为空")
|
||||
@@ -474,12 +488,20 @@ class ProductSuiteAiWriteWorker(BaseWorker):
|
||||
self.instruction = str(instruction or "")
|
||||
self.context = str(context or "")
|
||||
self.image_paths = [str(path or "") for path in list(image_paths or [])]
|
||||
self.config = config
|
||||
self.config = ai.freeze_runtime_config(
|
||||
config,
|
||||
cmhub_config_path=cmhub_config_path,
|
||||
models_path=(config or {}).get("ai_models_path", appconfig.AI_MODELS_PATH),
|
||||
include_cmhub=True,
|
||||
include_direct_models=False,
|
||||
)
|
||||
self.cmhub_config_path = cmhub_config_path
|
||||
|
||||
def execute(self):
|
||||
if self.should_cancel():
|
||||
return {"cancelled": True}
|
||||
if appconfig.ai_backend(self.config) != "cmhub":
|
||||
raise ValueError("商品套图AI帮写仅支持默认网关,请到⑤设置切换后再使用")
|
||||
result = ai.analyze_product_images(
|
||||
self.instruction,
|
||||
self.context,
|
||||
@@ -583,7 +605,13 @@ class ImageStudioResumeJobsWorker(BaseWorker):
|
||||
self.project_id = int(project_id) if project_id is not None else None
|
||||
self.aspect_ratio = str(aspect_ratio or "1:1")
|
||||
self.db_path = db_path
|
||||
self.config = config
|
||||
self.config = ai.freeze_runtime_config(
|
||||
config,
|
||||
cmhub_config_path=cmhub_config_path,
|
||||
models_path=(config or {}).get("ai_models_path", appconfig.AI_MODELS_PATH),
|
||||
include_cmhub=True,
|
||||
include_direct_models=False,
|
||||
)
|
||||
self.cmhub_config_path = cmhub_config_path
|
||||
self._done = 0
|
||||
self._failed = 0
|
||||
@@ -689,7 +717,11 @@ class GenerateWorker(BaseWorker):
|
||||
self.tasks = list(tasks)
|
||||
self.prompt_values = dict(prompt_values or {})
|
||||
self.db_path = db_path
|
||||
self.config = config
|
||||
self.config = ai.freeze_runtime_config(
|
||||
config,
|
||||
cmhub_config_path=(config or {}).get("cmhub_config_path", appconfig.CMHUB_CONFIG_PATH),
|
||||
models_path=(config or {}).get("ai_models_path", appconfig.AI_MODELS_PATH),
|
||||
)
|
||||
self.diagnostic_log_dir = diagnostic_log_dir
|
||||
self.generation_scope = product_status.normalize_scope(generation_scope)
|
||||
self.product_status_counts = {
|
||||
@@ -786,6 +818,8 @@ class GenerateWorker(BaseWorker):
|
||||
),
|
||||
excluded=self.status_scope_excluded,
|
||||
)
|
||||
if appconfig.ai_backend(self.config) == "direct":
|
||||
start_message += ";自定义网关(不计点数,费用由服务商收取)"
|
||||
self._log_run_event(start_message)
|
||||
try:
|
||||
summary = ai.generate_batch(
|
||||
|
||||
Reference in New Issue
Block a user