feat: add selected task reset actions

This commit is contained in:
chengma
2026-06-30 11:50:49 +08:00
parent 580a7a570d
commit f7ea9ec0fa
6 changed files with 412 additions and 8 deletions
+75
View File
@@ -518,6 +518,16 @@ def list_tasks(batch_id=None, stage=None, status=None, alias=None, path=None, co
return _fetch_all(database, sql, params, Task)
def get_task(task_id, path=None, conn=None):
with _connection(conn, path) as database:
return _fetch_one(
database,
"SELECT * FROM tasks WHERE id = ?",
(int(task_id),),
Task,
)
def mark_running(task_id, phase, path=None, conn=None) -> None:
_attempt_field(phase)
with _connection(conn, path) as database:
@@ -639,6 +649,71 @@ def set_applied(task_id, committed, error=None, path=None, conn=None) -> None:
)
def reset_generated(task_id, delete_file=False, path=None, conn=None) -> dict:
"""Clear local AI result and move the task back to collected state."""
with _connection(conn, path) as database:
before = get_task(task_id, conn=database)
if before is None:
raise DbError(f"任务不存在: {task_id}")
new_cover_path = before.new_cover_path
deleted_file = None
if delete_file:
raise DbError("重置生成结果默认不删除本地新封面文件,请手动确认后清理")
now = _now()
with database:
database.execute(
"""
UPDATE tasks
SET new_title = NULL,
new_cover_path = NULL,
stage = 'collected',
status = 'success',
last_error = NULL,
generated_at = NULL,
updated_at = ?
WHERE id = ?
""",
(now, int(task_id)),
)
after = get_task(task_id, conn=database)
return {
"task_id": int(task_id),
"before": before,
"after": after,
"new_cover_path": new_cover_path,
"deleted_file": deleted_file,
}
def reset_apply_status(task_id, path=None, conn=None) -> dict:
"""Move a generated/applied local task back to pending update state."""
with _connection(conn, path) as database:
before = get_task(task_id, conn=database)
if before is None:
raise DbError(f"任务不存在: {task_id}")
now = _now()
with database:
database.execute(
"""
UPDATE tasks
SET stage = 'generated',
status = 'pending',
last_error = NULL,
updated_at = ?
WHERE id = ?
""",
(now, int(task_id)),
)
after = get_task(task_id, conn=database)
return {
"task_id": int(task_id),
"before": before,
"after": after,
}
def create_run_log(
run_type,
dry_run=False,
+163
View File
@@ -104,6 +104,43 @@ if QT_IMPORT_ERROR is None:
return db_path or appconfig.db_path(config)
def _write_reset_run_log(db_path, task, action, message):
run_id = db.create_run_log(
"reset",
total=1,
options={
"action": action,
"task_id": getattr(task, "id", None),
"alias": getattr(task, "alias", None),
"item_id": getattr(task, "item_id", None),
},
path=db_path,
)
db.add_run_log_event(
run_id,
message,
task_id=getattr(task, "id", None),
alias=getattr(task, "alias", None),
item_id=getattr(task, "item_id", None),
path=db_path,
)
db.finish_run_log(
run_id,
status="done",
done=1,
success_count=1,
failed_count=0,
summary_json={
"action": action,
"task_id": getattr(task, "id", None),
"alias": getattr(task, "alias", None),
"item_id": getattr(task, "item_id", None),
},
path=db_path,
)
return run_id
class TaskTableModel(QAbstractTableModel):
"""Table model for task rows shared by workflow tabs."""
@@ -450,6 +487,8 @@ if QT_IMPORT_ERROR is None:
self.preview_prompt_button = QPushButton("预览")
self.generate_button = QPushButton("开始生成")
self.stop_generate_button = QPushButton("停止")
self.reset_generate_button = QPushButton("重置生成结果")
self.reset_generate_button.setObjectName("resetGenerateButton")
self.stop_generate_button.setEnabled(False)
self.progress_label = QLabel("进度:标题0/0 · 封面0/0 · 失败0")
@@ -535,6 +574,7 @@ if QT_IMPORT_ERROR is None:
bottom_layout.addStretch(1)
bottom_layout.addWidget(self.generate_button)
bottom_layout.addWidget(self.stop_generate_button)
bottom_layout.addWidget(self.reset_generate_button)
layout = QVBoxLayout(self)
layout.setContentsMargins(18, 18, 18, 18)
@@ -557,6 +597,7 @@ if QT_IMPORT_ERROR is None:
self.preview_prompt_button.clicked.connect(self.preview_cover_prompt)
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)
self.task_table.doubleClicked.connect(self.show_task_images)
self.refresh_cover_templates()
@@ -773,6 +814,61 @@ if QT_IMPORT_ERROR is None:
self.generate_worker.cancel()
self._set_status("正在停止 AI 生成...")
def reset_generated_result(self, checked=False):
if self.generate_thread is not None:
self._set_status("AI 生成正在进行,不能重置")
return
task = self._selected_task()
if task is None:
self._set_status("请选择要重置生成结果的任务")
return
has_ai_result = bool(getattr(task, "new_title", None) or getattr(task, "new_cover_path", None))
if not has_ai_result and getattr(task, "stage", None) not in {"generated", "applied"}:
self._set_status("选中任务没有可重置的生成结果")
return
lines = [
"确定重置当前选中任务的本地生成结果吗?",
"",
f"商品ID:{task.item_id}",
f"店铺:{self.model.account_name_for(task)}",
"",
"将清空新标题、新封面路径和错误信息,并退回到已采集状态。",
"默认不删除本地新封面文件,不触碰 Shopee,也不会自动回写 Excel。",
]
if getattr(task, "new_cover_path", None):
lines.append(f"本地新封面文件保留:{task.new_cover_path}")
if getattr(task, "committed", 0):
lines.extend([
"",
"注意:该记录曾经提交过线上。本地重置不会回滚 Shopee,后续重新生成/更新可能再次提交线上。",
])
answer = QMessageBox.question(
self,
"重置生成结果",
"\n".join(lines),
QMessageBox.Yes | QMessageBox.No,
QMessageBox.No,
)
if answer != QMessageBox.Yes:
self._set_status("已取消重置生成结果")
return
try:
db.reset_generated(task.id, path=self.db_path)
message = (
"action=reset_generated step=db_write result=success "
f"detail=清空AI生成结果 task_id={task.id}"
)
run_id = _write_reset_run_log(self.db_path, task, "reset_generated", message)
except Exception as exc:
QMessageBox.warning(self, "重置生成结果", str(exc))
self._set_status(f"重置生成结果失败:{exc}")
return
self.refresh_tasks()
self._append_generate_log(message)
self._set_status(
f"已重置生成结果:商品 {task.item_id},run_id={run_id}"
)
def show_task_images(self, index):
task = self.model.task_at(index.row()) if index.isValid() else self._selected_task()
if task is None:
@@ -820,6 +916,7 @@ if QT_IMPORT_ERROR is None:
def _set_generate_running(self, running):
self.generate_button.setEnabled(not running)
self.stop_generate_button.setEnabled(running)
self.reset_generate_button.setEnabled(not running)
self.refresh_button.setEnabled(not running)
self.batch_filter.setEnabled(not running)
self.shop_filter.setEnabled(not running)
@@ -1077,6 +1174,8 @@ if QT_IMPORT_ERROR is None:
self.start_update_button = QPushButton("开始更新")
self.stop_update_button = QPushButton("停止")
self.reset_update_button = QPushButton("重置更新状态")
self.reset_update_button.setObjectName("resetUpdateButton")
self.write_back_button = QPushButton("回写结果到 Excel")
self.stop_update_button.setEnabled(False)
self.write_back_button.setEnabled(False)
@@ -1084,6 +1183,7 @@ if QT_IMPORT_ERROR is None:
action_layout = QHBoxLayout()
action_layout.addWidget(self.start_update_button)
action_layout.addWidget(self.stop_update_button)
action_layout.addWidget(self.reset_update_button)
action_layout.addStretch(1)
action_layout.addWidget(self.write_back_button)
@@ -1104,6 +1204,7 @@ if QT_IMPORT_ERROR is None:
self.refresh_button.clicked.connect(self.refresh_tasks)
self.start_update_button.clicked.connect(self.start_update)
self.stop_update_button.clicked.connect(self.stop_update)
self.reset_update_button.clicked.connect(self.reset_apply_status)
self.write_back_button.clicked.connect(self.write_back_results)
self.refresh_tasks()
@@ -1217,6 +1318,66 @@ if QT_IMPORT_ERROR is None:
return
self._start_result_write_back(batch_ids, auto=False)
def reset_apply_status(self, checked=False):
if self.apply_thread is not None or self.result_write_back_thread is not None:
self._set_status("更新或回写正在进行,不能重置")
return
task = self._selected_task()
if task is None:
self._set_status("请选择要重置更新状态的任务")
return
if not (getattr(task, "new_title", None) or getattr(task, "new_cover_path", None)):
self._set_status("选中任务没有新标题或新封面,不能重置为可更新")
return
lines = [
"确定重置当前选中任务的本地更新状态吗?",
"",
f"商品ID:{task.item_id}",
f"店铺:{self.model.account_name_for(task)}",
"",
"将保留新标题和新封面路径,只把本地状态退回可更新。",
"不会触碰 Shopee,也不会自动回写 Excel。",
]
if getattr(task, "committed", 0):
lines.extend([
"",
"注意:该记录已经提交过线上。本地重置不会回滚 Shopee,重复更新会再次提交线上。",
])
answer = QMessageBox.question(
self,
"重置更新状态",
"\n".join(lines),
QMessageBox.Yes | QMessageBox.No,
QMessageBox.No,
)
if answer != QMessageBox.Yes:
self._set_status("已取消重置更新状态")
return
try:
db.reset_apply_status(task.id, path=self.db_path)
message = (
"action=reset_apply_status step=db_write result=success "
f"detail=退回可更新 task_id={task.id}"
)
run_id = _write_reset_run_log(self.db_path, task, "reset_apply_status", message)
except Exception as exc:
QMessageBox.warning(self, "重置更新状态", str(exc))
self._set_status(f"重置更新状态失败:{exc}")
return
self.refresh_tasks()
self._append_run_log(message)
self._set_status(
f"已重置更新状态:商品 {task.item_id},run_id={run_id}"
)
def _selected_task(self):
index = self.task_table.currentIndex()
if index.isValid():
return self.model.task_at(index.row())
if self.model.rowCount() > 0:
return self.model.task_at(0)
return None
def _is_actionable_task(self, task):
return (
getattr(task, "stage", None) == "generated"
@@ -1378,6 +1539,7 @@ if QT_IMPORT_ERROR is None:
def _set_apply_running(self, running):
self.start_update_button.setEnabled(not running)
self.stop_update_button.setEnabled(running)
self.reset_update_button.setEnabled(not running)
self.refresh_button.setEnabled(not running)
self.batch_filter.setEnabled(not running)
self.shop_filter.setEnabled(not running)
@@ -1387,6 +1549,7 @@ if QT_IMPORT_ERROR is None:
def _set_result_write_back_running(self, running):
self.start_update_button.setEnabled(not running)
self.reset_update_button.setEnabled(not running)
self.refresh_button.setEnabled(not running)
self.batch_filter.setEnabled(not running)
self.shop_filter.setEnabled(not running)