feat: split generate title and image statuses
This commit is contained in:
+104
-16
@@ -155,7 +155,7 @@ class TaskTableModel(QAbstractTableModel):
|
||||
class GenerateTaskTableModel(QAbstractTableModel):
|
||||
"""Table model for Tab 2 generation candidates."""
|
||||
|
||||
HEADERS = ["店铺", "商品ID", "旧标题", "新标题", "状态"]
|
||||
HEADERS = ["店铺", "商品ID", "旧标题", "新标题", "标题状态", "图片状态"]
|
||||
|
||||
STATUS_TEXT = {
|
||||
"running": "处理中",
|
||||
@@ -178,6 +178,15 @@ class GenerateTaskTableModel(QAbstractTableModel):
|
||||
self.db_path = db_path
|
||||
self.status_callback = status_callback
|
||||
self.last_edit_error = None
|
||||
self.generate_cover_enabled = False
|
||||
|
||||
def set_generate_cover_enabled(self, enabled):
|
||||
self.generate_cover_enabled = bool(enabled)
|
||||
if not self.tasks:
|
||||
return
|
||||
top_left = self.index(0, 4)
|
||||
bottom_right = self.index(len(self.tasks) - 1, 5)
|
||||
self.dataChanged.emit(top_left, bottom_right, [Qt.DisplayRole, Qt.ForegroundRole])
|
||||
|
||||
def set_tasks(self, tasks, accounts):
|
||||
self.beginResetModel()
|
||||
@@ -208,8 +217,8 @@ class GenerateTaskTableModel(QAbstractTableModel):
|
||||
task = self.tasks[index.row()]
|
||||
if role in (Qt.DisplayRole, Qt.EditRole):
|
||||
return self._display_value(task, index.column())
|
||||
if role == Qt.ForegroundRole and index.column() == 4:
|
||||
return self._status_color(task)
|
||||
if role == Qt.ForegroundRole and index.column() in {4, 5}:
|
||||
return self._component_status_color(self._display_value(task, index.column()))
|
||||
if role == Qt.ToolTipRole:
|
||||
if index.column() == 3 and self._can_edit_title(task):
|
||||
return "双击可微调新标题,只修改本地待更新内容"
|
||||
@@ -273,13 +282,6 @@ class GenerateTaskTableModel(QAbstractTableModel):
|
||||
return account.account_name
|
||||
return task.account_name or task.alias or ""
|
||||
|
||||
def _status_text(self, task):
|
||||
if task.status == "failed":
|
||||
return self._failed_status_text(task)
|
||||
if task.status in self.STATUS_TEXT and task.status != "pending":
|
||||
return self.STATUS_TEXT[task.status]
|
||||
return self.STAGE_TEXT.get(task.stage, task.stage)
|
||||
|
||||
def _failed_status_text(self, task):
|
||||
collect_attempts = self._attempt_count(task, "collect_attempts")
|
||||
generate_attempts = self._attempt_count(task, "generate_attempts")
|
||||
@@ -298,12 +300,97 @@ class GenerateTaskTableModel(QAbstractTableModel):
|
||||
except (TypeError, ValueError):
|
||||
return 0
|
||||
|
||||
def _status_color(self, task):
|
||||
base_color = _status_base_color(getattr(task, "status", None))
|
||||
if base_color is not None:
|
||||
return _qcolor(base_color)
|
||||
if getattr(task, "stage", None) in {"generated", "applied"}:
|
||||
def _has_new_title(self, task):
|
||||
return bool(str(getattr(task, "new_title", "") or "").strip())
|
||||
|
||||
def _has_new_cover(self, task):
|
||||
return bool(str(getattr(task, "new_cover_path", "") or "").strip())
|
||||
|
||||
def _generate_failed(self, task):
|
||||
return (
|
||||
getattr(task, "status", None) == "failed"
|
||||
and self._attempt_count(task, "generate_attempts") > 0
|
||||
)
|
||||
|
||||
def _last_error_text(self, task):
|
||||
return str(getattr(task, "last_error", "") or "").lower()
|
||||
|
||||
def _is_title_failure(self, task):
|
||||
if not self._generate_failed(task):
|
||||
return False
|
||||
error = self._last_error_text(task)
|
||||
if any(marker in error for marker in ("title", "标题", "生文", "文本")):
|
||||
return True
|
||||
return not self._has_new_title(task) and not self._is_cover_failure(task)
|
||||
|
||||
def _is_cover_failure(self, task):
|
||||
if not self._generate_failed(task):
|
||||
return False
|
||||
error = self._last_error_text(task)
|
||||
return any(
|
||||
marker in error
|
||||
for marker in ("cover", "封面", "图片", "图像", "新封面")
|
||||
)
|
||||
|
||||
def _is_collect_incomplete(self, task):
|
||||
return (
|
||||
getattr(task, "stage", None) == "imported"
|
||||
and self._attempt_count(task, "generate_attempts") == 0
|
||||
and not self._is_collect_failure(task)
|
||||
)
|
||||
|
||||
def _is_collect_failure(self, task):
|
||||
return (
|
||||
getattr(task, "status", None) == "failed"
|
||||
and self._attempt_count(task, "collect_attempts") > 0
|
||||
and self._attempt_count(task, "generate_attempts") == 0
|
||||
)
|
||||
|
||||
def _title_status_text(self, task):
|
||||
if getattr(task, "status", None) == "skipped":
|
||||
return "略过"
|
||||
if self._is_collect_failure(task):
|
||||
return "采集失败"
|
||||
if self._is_collect_incomplete(task):
|
||||
return "未采集"
|
||||
if self._is_title_failure(task):
|
||||
return "失败"
|
||||
if self._has_new_title(task):
|
||||
return "已生成"
|
||||
if getattr(task, "status", None) == "running":
|
||||
return "处理中"
|
||||
if getattr(task, "stage", None) == "applied":
|
||||
return "已生成" if self._has_new_title(task) else "未生成"
|
||||
return "待生成"
|
||||
|
||||
def _cover_status_text(self, task):
|
||||
if getattr(task, "status", None) == "skipped":
|
||||
return "略过"
|
||||
if self._is_collect_failure(task):
|
||||
return "采集失败"
|
||||
if self._is_collect_incomplete(task):
|
||||
return "未采集"
|
||||
if self._has_new_cover(task):
|
||||
return "已生成"
|
||||
if self._is_cover_failure(task):
|
||||
return "失败"
|
||||
if not self.generate_cover_enabled:
|
||||
return "未启用"
|
||||
if self._is_title_failure(task) or not self._has_new_title(task):
|
||||
return "未开始"
|
||||
if getattr(task, "status", None) == "running":
|
||||
return "处理中"
|
||||
return "待生成"
|
||||
|
||||
def _component_status_color(self, text):
|
||||
if text in {"失败", "采集失败"}:
|
||||
return _qcolor(COLOR_DANGER)
|
||||
if text == "处理中":
|
||||
return _qcolor(COLOR_INFO)
|
||||
if text == "已生成":
|
||||
return _qcolor(COLOR_SUCCESS)
|
||||
if text in {"未启用", "未采集", "未开始", "略过", "已取消"}:
|
||||
return _qcolor(COLOR_MUTED)
|
||||
return _qcolor(COLOR_PENDING)
|
||||
|
||||
def _display_value(self, task, column):
|
||||
@@ -312,7 +399,8 @@ class GenerateTaskTableModel(QAbstractTableModel):
|
||||
task.item_id,
|
||||
task.old_title or "",
|
||||
task.new_title or "",
|
||||
self._status_text(task),
|
||||
self._title_status_text(task),
|
||||
self._cover_status_text(task),
|
||||
]
|
||||
return values[column] if 0 <= column < len(values) else None
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ class GenerateTab(QWidget):
|
||||
"cover": "封面",
|
||||
"all": "标题和封面",
|
||||
}
|
||||
TABLE_COLUMN_WEIGHTS = (1.0, 0.5, 1.0, 1.0, 1.25, 1.25)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -171,11 +172,13 @@ 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.task_table.setModel(self.model)
|
||||
self.task_table.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||
self.task_table.setSelectionMode(QAbstractItemView.ExtendedSelection)
|
||||
self.task_table.setEditTriggers(QAbstractItemView.DoubleClicked | QAbstractItemView.EditKeyPressed)
|
||||
self.task_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
||||
self.task_table.horizontalHeader().setSectionResizeMode(QHeaderView.Interactive)
|
||||
self.task_table.horizontalHeader().setStretchLastSection(False)
|
||||
self.task_table.verticalHeader().setVisible(False)
|
||||
|
||||
self.run_log_view = QPlainTextEdit()
|
||||
@@ -201,6 +204,7 @@ class GenerateTab(QWidget):
|
||||
self.splitter.setStretchFactor(0, 1)
|
||||
self.splitter.setStretchFactor(1, 3)
|
||||
self.splitter.setSizes([280, 860])
|
||||
self.splitter.splitterMoved.connect(lambda *_: self._apply_task_table_column_widths())
|
||||
|
||||
title_progress_layout = QHBoxLayout()
|
||||
title_progress_layout.addWidget(self.title_progress_label)
|
||||
@@ -256,6 +260,10 @@ class GenerateTab(QWidget):
|
||||
self._show_current_run_log_empty()
|
||||
self._update_cmhub_balance_label({})
|
||||
|
||||
def resizeEvent(self, event):
|
||||
super().resizeEvent(event)
|
||||
self._apply_task_table_column_widths()
|
||||
|
||||
def _set_status(self, message, level=None):
|
||||
_emit_status(self.status_callback, message, level=level)
|
||||
|
||||
@@ -281,11 +289,31 @@ class GenerateTab(QWidget):
|
||||
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))
|
||||
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)
|
||||
|
||||
def _apply_task_table_column_widths(self):
|
||||
if self.model.columnCount() != len(self.TABLE_COLUMN_WEIGHTS):
|
||||
return
|
||||
width = max(0, int(self.task_table.viewport().width()))
|
||||
if width <= 0:
|
||||
return
|
||||
total_weight = sum(self.TABLE_COLUMN_WEIGHTS)
|
||||
used = 0
|
||||
last_column = len(self.TABLE_COLUMN_WEIGHTS) - 1
|
||||
header = self.task_table.horizontalHeader()
|
||||
for column, weight in enumerate(self.TABLE_COLUMN_WEIGHTS):
|
||||
if column == last_column:
|
||||
section_width = max(1, width - used)
|
||||
else:
|
||||
section_width = max(1, int(width * weight / total_weight))
|
||||
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())
|
||||
@@ -964,7 +992,9 @@ 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_tasks(filtered_tasks, accounts_rows)
|
||||
self._apply_task_table_column_widths()
|
||||
self.summary_label.setText(
|
||||
f"任务 {len(filtered_tasks)}/{len(batch_tasks)} 条"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user