feat: show title generation wait timer

This commit is contained in:
2026-07-04 16:47:12 +08:00
parent 58343ce4d6
commit b5f289fddd
3 changed files with 135 additions and 12 deletions
+74 -2
View File
@@ -7,8 +7,9 @@
通过 Qt signal 回主线程刷新 UI(子线程不直接碰控件)。
"""
import logging
import time
from PySide6.QtCore import QObject, QSize, Qt, QThread, QUrl, Signal
from PySide6.QtCore import QObject, QSize, Qt, QThread, QTimer, QUrl, Signal
from PySide6.QtGui import QDesktopServices, QIcon, QPixmap
from PySide6.QtWidgets import (
QAbstractItemView,
@@ -322,6 +323,12 @@ class AiOutfitPanel(QWidget):
self._title_worker = None
self._title_fail = 0 # running failure count for title stats
self._title_model_name = "" # configured 标题模型 name (§17.3 配置定名)
self._title_wait_started_at = 0.0
self._title_wait_limit_seconds = 0
self._title_wait_generation = 0
self._title_wait_timer = QTimer(self)
self._title_wait_timer.setInterval(1000)
self._title_wait_timer.timeout.connect(self._refresh_title_wait_label)
self._preview_dialog = None # 最终提示词预览弹窗(§7.3,按需)
self._row_to_table = {} # excel row_index -> table row
self._failures = [] # list of OutfitResult (failed)
@@ -471,7 +478,15 @@ class AiOutfitPanel(QWidget):
"""
box = QGroupBox()
v = QVBoxLayout(box)
v.addWidget(QLabel("标题生成提示词"))
header = QHBoxLayout()
header.addWidget(QLabel("标题生成提示词"))
header.addStretch(1)
self._title_wait_label = QLabel("")
self._title_wait_label.setObjectName("titleWaitLabel")
self._title_wait_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
self._title_wait_label.setVisible(False)
header.addWidget(self._title_wait_label)
v.addLayout(header)
self._title_prompt_edit = QPlainTextEdit()
self._title_prompt_edit.setMinimumHeight(110)
v.addWidget(self._title_prompt_edit)
@@ -987,6 +1002,7 @@ class AiOutfitPanel(QWidget):
self._title_fail = 0
self._progress.setValue(0)
self._log.clear()
self._start_title_wait_timer(self._title_wait_limit_for_model(model_config))
self._title_worker = _TitleWorker(excel, model_config, prompt)
self._title_thread = QThread(self)
@@ -1009,6 +1025,10 @@ class AiOutfitPanel(QWidget):
self._append_log("标题生成:已加载 {} 行".format(len(tasks)))
def _on_title_progress(self, completed, total, result):
if self._title_wait_timer.isActive():
self._title_wait_timer.stop()
self._set_title_wait_text("写入中 {}/{}".format(completed, total))
row = self._row_to_table.get(result.task.row_index)
if row is not None:
if result.success:
@@ -1025,6 +1045,10 @@ class AiOutfitPanel(QWidget):
def _on_title_finished(self, success, fail):
self._set_title_running(False)
if fail:
self._finish_title_wait("完成 {} 条,失败 {}".format(success, fail))
else:
self._finish_title_wait("完成 {} 条".format(success))
msg = "标题生成结束:成功 {},失败 {}".format(success, fail)
self._append_log(msg)
self._reload_after_titles()
@@ -1037,10 +1061,13 @@ class AiOutfitPanel(QWidget):
def _on_title_failed(self, message):
self._set_title_running(False)
self._finish_title_wait("生成失败")
self._append_log("标题生成无法开始:" + message)
QMessageBox.warning(self, "无法开始", message)
def _cleanup_title_thread(self):
if self._title_wait_timer.isActive():
self._reset_title_wait_label()
self._title_thread = None
self._title_worker = None
@@ -1064,6 +1091,51 @@ class AiOutfitPanel(QWidget):
self._start_btn.setEnabled(not running) # mutually exclusive with 开始生成
self._excel_edit.setEnabled(not running)
def _title_wait_limit_for_model(self, model_config):
"""Return the title request wait ceiling shown in the label (§17.10)."""
try:
override = int(getattr(model_config, "timeout_seconds", 0) or 0)
except (TypeError, ValueError):
override = 0
if override > 0:
return override
from services.ai_image_service import resolution_timeout
return resolution_timeout("4K")
def _start_title_wait_timer(self, limit_seconds):
self._title_wait_generation += 1
self._title_wait_limit_seconds = max(1, int(limit_seconds or 0))
self._title_wait_started_at = time.monotonic()
self._refresh_title_wait_label()
self._title_wait_timer.start()
def _refresh_title_wait_label(self):
elapsed = max(0, int(time.monotonic() - self._title_wait_started_at))
self._set_title_wait_text("等待中 {} / {}".format(
self._format_wait_seconds(elapsed),
self._format_wait_seconds(self._title_wait_limit_seconds)))
def _set_title_wait_text(self, text):
self._title_wait_label.setText(str(text))
self._title_wait_label.setVisible(bool(text))
def _finish_title_wait(self, text):
self._title_wait_timer.stop()
self._set_title_wait_text(text)
token = self._title_wait_generation
QTimer.singleShot(4000, lambda: self._reset_title_wait_label(token))
def _reset_title_wait_label(self, token=None):
if token is not None and token != self._title_wait_generation:
return
self._title_wait_timer.stop()
self._title_wait_label.clear()
self._title_wait_label.setVisible(False)
def _format_wait_seconds(self, seconds):
total = max(0, int(seconds or 0))
return "{:02d}:{:02d}".format(total // 60, total % 60)
# -- switch info popups (user-only; §10.2) --------------------------
def _on_resolution_activated(self, _index):