fix(product-suite): keep retries in current results
This commit is contained in:
+160
-37
@@ -697,6 +697,9 @@ class SuiteTaskState:
|
||||
settings: dict = field(default_factory=product_suite.default_suite_settings)
|
||||
current_job_ids: list = field(default_factory=list)
|
||||
show_history: bool = False
|
||||
generation_job_ids: list = field(default_factory=list)
|
||||
generation_mode: str = "batch"
|
||||
generation_retry_job_id: int = None
|
||||
worker: object = None
|
||||
thread: object = None
|
||||
generation_run_token: str = ""
|
||||
@@ -1582,6 +1585,9 @@ class ProductSuiteTab(QWidget):
|
||||
state.project_binding_state = ""
|
||||
state.last_saved_prompt = ""
|
||||
state.current_job_ids = []
|
||||
state.generation_job_ids = []
|
||||
state.generation_mode = "batch"
|
||||
state.generation_retry_job_id = None
|
||||
state.done = state.failed = state.total = 0
|
||||
state.started_at = None
|
||||
if state is self._displayed_state:
|
||||
@@ -2517,12 +2523,22 @@ class ProductSuiteTab(QWidget):
|
||||
if state is None:
|
||||
return
|
||||
if state.generation_running():
|
||||
retrying = state.generation_mode == "retry"
|
||||
if state.generation_stop_requested:
|
||||
self._status("正在停止当前套图任务", "warning")
|
||||
self._status(
|
||||
"正在停止当前图片重试"
|
||||
if retrying
|
||||
else "正在停止当前套图任务",
|
||||
"warning",
|
||||
)
|
||||
return
|
||||
if self._confirm(
|
||||
"停止生成套图",
|
||||
"确认取消当前任务吗?已提交任务会在安全边界停止。",
|
||||
"停止图片重试" if retrying else "停止生成套图",
|
||||
(
|
||||
"确认停止当前图片重试吗?已提交任务会在安全边界停止。"
|
||||
if retrying
|
||||
else "确认取消当前任务吗?已提交任务会在安全边界停止。"
|
||||
),
|
||||
destructive=True,
|
||||
):
|
||||
state.generation_stop_requested = True
|
||||
@@ -2533,14 +2549,21 @@ class ProductSuiteTab(QWidget):
|
||||
"stop_requested",
|
||||
)
|
||||
self._apply_running_state(state)
|
||||
self._status("已请求停止当前套图任务", "warning")
|
||||
self._status(
|
||||
"已请求停止当前图片重试"
|
||||
if retrying
|
||||
else "已请求停止当前套图任务",
|
||||
"warning",
|
||||
)
|
||||
return
|
||||
self.start_generation(state)
|
||||
|
||||
def start_generation(self, state, specs=None):
|
||||
def start_generation(self, state, specs=None, *, retry_job_id=None):
|
||||
if state.generation_running():
|
||||
self._status("当前套图任务仍在生成", "warning")
|
||||
return False
|
||||
retry_job_id = int(retry_job_id) if retry_job_id is not None else None
|
||||
retrying = retry_job_id is not None
|
||||
if state is self._displayed_state:
|
||||
self._save_controls_to_state(state)
|
||||
template_text = None
|
||||
@@ -2595,12 +2618,16 @@ class ProductSuiteTab(QWidget):
|
||||
state.generation_run_token = run_token
|
||||
state.generation_stop_requested = False
|
||||
state.generation_terminal_streak = 0
|
||||
state.generation_job_ids = []
|
||||
state.generation_mode = "retry" if retrying else "batch"
|
||||
state.generation_retry_job_id = retry_job_id
|
||||
state.done = 0
|
||||
state.failed = 0
|
||||
state.total = len(specs)
|
||||
state.started_at = time.monotonic()
|
||||
state.current_job_ids = []
|
||||
state.show_history = False
|
||||
if not retrying:
|
||||
state.current_job_ids = []
|
||||
state.show_history = False
|
||||
self._generation_run_states[run_token] = state.key
|
||||
worker.progress.connect(self._on_generation_progress_signal)
|
||||
worker.finished.connect(self._on_generation_finished_signal)
|
||||
@@ -2611,17 +2638,27 @@ class ProductSuiteTab(QWidget):
|
||||
state,
|
||||
run_token,
|
||||
"started",
|
||||
{"total": len(specs), "job_ids": 0},
|
||||
{
|
||||
"total": len(specs),
|
||||
"job_ids": 0,
|
||||
"mode": state.generation_mode,
|
||||
},
|
||||
)
|
||||
if state is self._displayed_state:
|
||||
if state is self._displayed_state and not retrying:
|
||||
self._loading = True
|
||||
try:
|
||||
self.history_button.setChecked(False)
|
||||
finally:
|
||||
self._loading = False
|
||||
if state is self._displayed_state:
|
||||
self._apply_running_state(state)
|
||||
self._refresh_results(state)
|
||||
self._status("商品套图生成已开始,共%d张;可切换到其他任务" % len(specs), "info")
|
||||
self._status(
|
||||
"图片重试已开始;可切换到其他任务"
|
||||
if retrying
|
||||
else "商品套图生成已开始,共%d张;可切换到其他任务" % len(specs),
|
||||
"info",
|
||||
)
|
||||
return True
|
||||
|
||||
def _generation_signal_token(self, payload=None):
|
||||
@@ -2651,7 +2688,7 @@ class ProductSuiteTab(QWidget):
|
||||
state.failed = int(payload.get("failed", state.failed) or 0)
|
||||
job_ids = payload.get("job_ids")
|
||||
if job_ids is not None:
|
||||
state.current_job_ids = [int(job_id) for job_id in job_ids]
|
||||
self._set_generation_job_ids(state, job_ids)
|
||||
state.generation_terminal_streak = 0
|
||||
if state is self._displayed_state:
|
||||
self._refresh_results(state)
|
||||
@@ -2670,7 +2707,15 @@ class ProductSuiteTab(QWidget):
|
||||
{"has_error": True},
|
||||
level="ERROR",
|
||||
)
|
||||
self._status("商品套图生成失败:%s" % _user_error(error), "danger")
|
||||
self._status(
|
||||
(
|
||||
"图片重试失败:%s"
|
||||
if state.generation_mode == "retry"
|
||||
else "商品套图生成失败:%s"
|
||||
)
|
||||
% _user_error(error),
|
||||
"danger",
|
||||
)
|
||||
|
||||
@Slot(dict)
|
||||
def _on_generation_finished_signal(self, result):
|
||||
@@ -2678,6 +2723,8 @@ class ProductSuiteTab(QWidget):
|
||||
state = self._generation_state(token)
|
||||
if state is None:
|
||||
return
|
||||
if result.get("job_ids") is not None:
|
||||
self._set_generation_job_ids(state, result.get("job_ids"))
|
||||
self._log_generation_lifecycle(
|
||||
state,
|
||||
token,
|
||||
@@ -2779,7 +2826,7 @@ class ProductSuiteTab(QWidget):
|
||||
)
|
||||
|
||||
def _generation_job_ids(self, state):
|
||||
job_ids = list(state.current_job_ids)
|
||||
job_ids = list(state.generation_job_ids)
|
||||
if not job_ids and state.worker is not None:
|
||||
worker_job_ids = getattr(state.worker, "job_ids", [])
|
||||
if not isinstance(worker_job_ids, (list, tuple, set)):
|
||||
@@ -2789,9 +2836,30 @@ class ProductSuiteTab(QWidget):
|
||||
for job_id in list(worker_job_ids or [])
|
||||
]
|
||||
if job_ids:
|
||||
state.current_job_ids = job_ids
|
||||
self._set_generation_job_ids(state, job_ids)
|
||||
return job_ids
|
||||
|
||||
def _set_generation_job_ids(self, state, job_ids):
|
||||
normalized = []
|
||||
for job_id in job_ids or []:
|
||||
value = int(job_id)
|
||||
if value not in normalized:
|
||||
normalized.append(value)
|
||||
state.generation_job_ids = normalized
|
||||
if state.generation_mode != "retry":
|
||||
state.current_job_ids = list(normalized)
|
||||
return
|
||||
retry_job_id = state.generation_retry_job_id
|
||||
current = [int(job_id) for job_id in state.current_job_ids]
|
||||
if retry_job_id in current:
|
||||
index = current.index(retry_job_id)
|
||||
current[index:index + 1] = normalized
|
||||
elif not state.show_history:
|
||||
for job_id in normalized:
|
||||
if job_id not in current:
|
||||
current.append(job_id)
|
||||
state.current_job_ids = current
|
||||
|
||||
def _generation_job_snapshot(self, state):
|
||||
job_ids = self._generation_job_ids(state)
|
||||
counts = {
|
||||
@@ -2865,6 +2933,7 @@ class ProductSuiteTab(QWidget):
|
||||
def _finalize_generation(self, state, run_token, result, *, source):
|
||||
if self._generation_state(run_token) is not state:
|
||||
return False
|
||||
retrying = state.generation_mode == "retry"
|
||||
snapshot = self._generation_job_snapshot(state)
|
||||
if snapshot["job_ids"] and (
|
||||
snapshot["all_terminal"]
|
||||
@@ -2890,6 +2959,8 @@ class ProductSuiteTab(QWidget):
|
||||
state.generation_run_token = ""
|
||||
state.generation_stop_requested = False
|
||||
state.generation_terminal_streak = 0
|
||||
state.generation_job_ids = []
|
||||
state.generation_retry_job_id = None
|
||||
state.worker = None
|
||||
state.thread = None
|
||||
state.done = success + failed + cancelled
|
||||
@@ -2912,6 +2983,7 @@ class ProductSuiteTab(QWidget):
|
||||
"cancelled": cancelled,
|
||||
"active": active,
|
||||
"elapsed_seconds": elapsed,
|
||||
"mode": "retry" if retrying else "batch",
|
||||
},
|
||||
level="WARNING" if active or result.get("ok") is False else "INFO",
|
||||
)
|
||||
@@ -2923,39 +2995,76 @@ class ProductSuiteTab(QWidget):
|
||||
or "生成线程已结束,部分任务可稍后继续查询"
|
||||
)
|
||||
if state is self._displayed_state:
|
||||
self._message("商品套图生成未完整结束", _user_error(message))
|
||||
self._message(
|
||||
"图片重试未完整结束"
|
||||
if retrying
|
||||
else "商品套图生成未完整结束",
|
||||
_user_error(message),
|
||||
)
|
||||
else:
|
||||
self._status(
|
||||
"套图任务%d生成未完整结束" % state.serial,
|
||||
(
|
||||
"套图任务%d图片重试未完整结束"
|
||||
if retrying
|
||||
else "套图任务%d生成未完整结束"
|
||||
)
|
||||
% state.serial,
|
||||
"danger",
|
||||
)
|
||||
return True
|
||||
if stop_requested or cancelled:
|
||||
if state is self._displayed_state:
|
||||
self._message(
|
||||
"商品套图生成已停止",
|
||||
"本轮共%d张:成功%d张,失败%d张,停止%d张;"
|
||||
"已提交任务可稍后继续查询;总用时%d秒。"
|
||||
% (total, success, failed, cancelled, elapsed),
|
||||
icon=QMessageBox.Information,
|
||||
)
|
||||
if retrying:
|
||||
self._message(
|
||||
"图片重试已停止",
|
||||
"本次重试:成功%d张,失败%d张,停止%d张;"
|
||||
"已提交任务可稍后继续查询;总用时%d秒。"
|
||||
% (success, failed, cancelled, elapsed),
|
||||
icon=QMessageBox.Information,
|
||||
)
|
||||
else:
|
||||
self._message(
|
||||
"商品套图生成已停止",
|
||||
"本轮共%d张:成功%d张,失败%d张,停止%d张;"
|
||||
"已提交任务可稍后继续查询;总用时%d秒。"
|
||||
% (total, success, failed, cancelled, elapsed),
|
||||
icon=QMessageBox.Information,
|
||||
)
|
||||
self._status(
|
||||
"商品套图生成已停止:成功%d张,失败%d张,停止%d张"
|
||||
(
|
||||
"图片重试已停止:成功%d张,失败%d张,停止%d张"
|
||||
if retrying
|
||||
else "商品套图生成已停止:成功%d张,失败%d张,停止%d张"
|
||||
)
|
||||
% (success, failed, cancelled),
|
||||
"warning",
|
||||
)
|
||||
return True
|
||||
if state is self._displayed_state:
|
||||
self._message(
|
||||
"商品套图生成完成",
|
||||
"本轮共%d张:成功%d张,失败%d张,停止%d张;总用时%d秒。"
|
||||
% (total, success, failed, cancelled, elapsed),
|
||||
icon=QMessageBox.Information,
|
||||
if retrying:
|
||||
self._message(
|
||||
"图片重试成功" if success and not failed else "图片重试失败",
|
||||
"本次重试:成功%d张,失败%d张,停止%d张;总用时%d秒。"
|
||||
% (success, failed, cancelled, elapsed),
|
||||
icon=QMessageBox.Information,
|
||||
)
|
||||
else:
|
||||
self._message(
|
||||
"商品套图生成完成",
|
||||
"本轮共%d张:成功%d张,失败%d张,停止%d张;总用时%d秒。"
|
||||
% (total, success, failed, cancelled, elapsed),
|
||||
icon=QMessageBox.Information,
|
||||
)
|
||||
if retrying:
|
||||
self._status(
|
||||
"图片重试成功" if success and not failed else "图片重试失败,请查看失败卡片",
|
||||
"success" if success and not failed else "danger",
|
||||
)
|
||||
else:
|
||||
self._status(
|
||||
"商品套图生成完成:成功%d张,失败%d张" % (success, failed),
|
||||
"success",
|
||||
)
|
||||
self._status(
|
||||
"商品套图生成完成:成功%d张,失败%d张" % (success, failed),
|
||||
"success",
|
||||
)
|
||||
return True
|
||||
|
||||
def _log_generation_lifecycle(
|
||||
@@ -3012,6 +3121,8 @@ class ProductSuiteTab(QWidget):
|
||||
self.generate_button.setText(
|
||||
"正在停止..."
|
||||
if state.generation_stop_requested
|
||||
else "停止重试"
|
||||
if state.generation_mode == "retry"
|
||||
else "停止生成"
|
||||
)
|
||||
self.generate_button.setStyleSheet(
|
||||
@@ -3035,7 +3146,8 @@ class ProductSuiteTab(QWidget):
|
||||
return
|
||||
elapsed = int(max(0, time.monotonic() - state.started_at)) if state.started_at else 0
|
||||
self.elapsed_label.setText(
|
||||
"套图 %d/%d(%d秒) · 失败 %d" % (
|
||||
"%s %d/%d(%d秒) · 失败 %d" % (
|
||||
"重试" if state.generation_mode == "retry" else "套图",
|
||||
state.done,
|
||||
state.total,
|
||||
elapsed,
|
||||
@@ -3067,8 +3179,12 @@ class ProductSuiteTab(QWidget):
|
||||
return []
|
||||
if state.show_history:
|
||||
return jobs
|
||||
current = set(state.current_job_ids)
|
||||
return [job for job in jobs if int(job.id) in current]
|
||||
by_id = {int(job.id): job for job in jobs}
|
||||
return [
|
||||
by_id[int(job_id)]
|
||||
for job_id in state.current_job_ids
|
||||
if int(job_id) in by_id
|
||||
]
|
||||
|
||||
def _refresh_results(self, state):
|
||||
while self.result_grid.count():
|
||||
@@ -3122,12 +3238,19 @@ class ProductSuiteTab(QWidget):
|
||||
if state.generation_running():
|
||||
self._message("当前任务正在生成", "请等待当前生成结束或停止后再重试单张图片。")
|
||||
return
|
||||
if str(getattr(job, "status", "") or "") not in {
|
||||
"failed",
|
||||
"expired",
|
||||
"cancelled",
|
||||
}:
|
||||
self._status("当前图片无需重试", "warning")
|
||||
return
|
||||
spec = {
|
||||
"source_asset_id": job.source_asset_id,
|
||||
"job_type": job.job_type,
|
||||
"prompt": job.prompt,
|
||||
}
|
||||
self.start_generation(state, specs=[spec])
|
||||
self.start_generation(state, specs=[spec], retry_job_id=job.id)
|
||||
|
||||
def _show_job_menu(self, job, global_position):
|
||||
menu = QMenu(self)
|
||||
|
||||
+5
-2
@@ -3,7 +3,7 @@ id: T-640
|
||||
title: 商品套图单张失败重试保持当前轮结果
|
||||
phase: 7
|
||||
deps: [T-639]
|
||||
status: TODO
|
||||
status: DONE
|
||||
created: 2026-07-16
|
||||
---
|
||||
|
||||
@@ -107,4 +107,7 @@ git diff --check
|
||||
|
||||
## 执行记录
|
||||
|
||||
- 待执行。
|
||||
- 2026-07-16:`SuiteTaskState` 新增内存态 `generation_job_ids`、`generation_mode` 与 `generation_retry_job_id`。`generation_job_ids` 专门供当前活动请求的进度、停止、线程结束和终态看门狗使用;`current_job_ids` 只表示当前结果区的有效槽位,解决单张重试时旧成功 job 被错误纳入本次汇总的问题。未修改 SQLite schema。
|
||||
- 2026-07-16:失败、过期或停止卡片的「重试」改为显式单张重试模式。worker 仍新建 job 保留历史和计费记录;新 job ID 返回后会原位替换当前结果区中的旧失败 job,其他成功图片保持不变。当前结果改按 `current_job_ids` 顺序展示;历史视图继续展示原失败与每次重试记录,从历史发起重试时不会强制退出历史视图。
|
||||
- 2026-07-16:重试运行状态、停止确认、进度文案和完成弹窗改为「图片重试」语义;重试成功、失败、停止和未完整结束分别给中文反馈。正常整轮生成、停止、T-639 `run_token` 隔离与终态兜底保持原行为。
|
||||
- 2026-07-16:补齐当前轮原位替换、历史记录保留、活动快照只统计新 job、重试再次失败仍可继续重试、历史视图保持和真实 QThread 完整退出测试。相关 42 项测试通过;在不包含用户未提交默认提示词改名的临时隔离 worktree 中,全量 535 项 unittest 通过,Ruff、`compileall` 与 `git diff --check` 均通过。
|
||||
|
||||
@@ -856,6 +856,7 @@ class ProductSuiteGuiTests(TempDirMixin, unittest.TestCase):
|
||||
state.thread = mock.Mock()
|
||||
state.generation_run_token = "watchdog-run"
|
||||
state.current_job_ids = [job.id for job in jobs]
|
||||
state.generation_job_ids = [job.id for job in jobs]
|
||||
state.total = len(jobs)
|
||||
state.started_at = time.monotonic()
|
||||
tab._generation_run_states["watchdog-run"] = state.key
|
||||
@@ -972,6 +973,7 @@ class ProductSuiteGuiTests(TempDirMixin, unittest.TestCase):
|
||||
state.thread = mock.Mock()
|
||||
state.generation_run_token = "thread-fallback"
|
||||
state.current_job_ids = [job.id]
|
||||
state.generation_job_ids = [job.id]
|
||||
state.total = 1
|
||||
state.started_at = time.monotonic()
|
||||
tab._generation_run_states["thread-fallback"] = state.key
|
||||
@@ -1127,6 +1129,260 @@ class ProductSuiteGuiTests(TempDirMixin, unittest.TestCase):
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_failed_job_retry_replaces_current_slot_and_keeps_history(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
config = self._config(temp_dir)
|
||||
project, sources = self._create_project_with_assets(temp_dir, config, 1)
|
||||
source = sources[0]
|
||||
|
||||
success_path = os.path.join(temp_dir, "success.jpg")
|
||||
retry_path = os.path.join(temp_dir, "retry.jpg")
|
||||
self._write_image(success_path)
|
||||
self._write_image(retry_path)
|
||||
success_asset = image_studio.add_asset(
|
||||
project.id,
|
||||
"generated_main",
|
||||
local_path=success_path,
|
||||
parent_asset_id=source.id,
|
||||
path=config["db_path"],
|
||||
)
|
||||
success_job = image_studio.create_job(
|
||||
project.id,
|
||||
source_asset_id=source.id,
|
||||
job_type="白底图",
|
||||
prompt="成功图",
|
||||
path=config["db_path"],
|
||||
)
|
||||
success_job = image_studio.update_job_status(
|
||||
success_job.id,
|
||||
"succeeded",
|
||||
output_asset_id=success_asset.id,
|
||||
path=config["db_path"],
|
||||
)
|
||||
failed_job = image_studio.create_job(
|
||||
project.id,
|
||||
source_asset_id=source.id,
|
||||
job_type="场景图",
|
||||
prompt="失败图",
|
||||
path=config["db_path"],
|
||||
)
|
||||
failed_job = image_studio.update_job_status(
|
||||
failed_job.id,
|
||||
"failed",
|
||||
error="上游生成失败",
|
||||
path=config["db_path"],
|
||||
)
|
||||
|
||||
tab = ProductSuiteTab(config=config, db_path=config["db_path"])
|
||||
self.addCleanup(tab.close)
|
||||
state = tab._displayed_state
|
||||
state.account_alias = "alias-a"
|
||||
state.item_id = project.item_id
|
||||
state.project_id = project.id
|
||||
state.project_binding_state = project.binding_state
|
||||
state.current_job_ids = [success_job.id, failed_job.id]
|
||||
tab._load_state(state)
|
||||
messages = []
|
||||
tab._message = lambda title, message, **kwargs: messages.append(
|
||||
(title, message)
|
||||
)
|
||||
|
||||
def fake_run_jobs(jobs, **kwargs):
|
||||
job = list(jobs)[0]
|
||||
retry_asset = image_studio.add_asset(
|
||||
project.id,
|
||||
"generated_main",
|
||||
local_path=retry_path,
|
||||
parent_asset_id=source.id,
|
||||
path=config["db_path"],
|
||||
)
|
||||
image_studio.update_job_status(
|
||||
job.id,
|
||||
"succeeded",
|
||||
output_asset_id=retry_asset.id,
|
||||
path=config["db_path"],
|
||||
)
|
||||
return {
|
||||
"total": 1,
|
||||
"success": 1,
|
||||
"failed": 0,
|
||||
"cancelled": 0,
|
||||
"jobs": [],
|
||||
}
|
||||
|
||||
with mock.patch(
|
||||
"app.gui.workers.image_studio_generation.run_jobs",
|
||||
side_effect=fake_run_jobs,
|
||||
):
|
||||
tab.retry_job(failed_job)
|
||||
generation_thread = state.thread
|
||||
deadline = time.monotonic() + 3
|
||||
while state.worker is not None and time.monotonic() < deadline:
|
||||
QTest.qWait(20)
|
||||
self.app.processEvents()
|
||||
while (
|
||||
generation_thread is not None
|
||||
and generation_thread.isRunning()
|
||||
and time.monotonic() < deadline
|
||||
):
|
||||
QTest.qWait(20)
|
||||
self.app.processEvents()
|
||||
self.assertFalse(generation_thread.isRunning())
|
||||
|
||||
all_jobs = image_studio.list_jobs(project.id, path=config["db_path"])
|
||||
retry_jobs = [
|
||||
job
|
||||
for job in all_jobs
|
||||
if job.id not in {success_job.id, failed_job.id}
|
||||
]
|
||||
self.assertEqual(1, len(retry_jobs))
|
||||
retry_job = retry_jobs[0]
|
||||
self.assertEqual(
|
||||
[success_job.id, retry_job.id],
|
||||
state.current_job_ids,
|
||||
)
|
||||
self.assertEqual([], state.generation_job_ids)
|
||||
self.assertEqual(
|
||||
[success_job.id, retry_job.id],
|
||||
[job.id for job in tab._jobs_for_state(state)],
|
||||
)
|
||||
self.assertEqual("图片重试成功", messages[-1][0])
|
||||
|
||||
state.show_history = True
|
||||
history_ids = {job.id for job in tab._jobs_for_state(state)}
|
||||
self.assertEqual(
|
||||
{success_job.id, failed_job.id, retry_job.id},
|
||||
history_ids,
|
||||
)
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_retry_tracks_only_new_job_and_preserves_history_view(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
config = self._config(temp_dir)
|
||||
project, sources = self._create_project_with_assets(temp_dir, config, 1)
|
||||
source = sources[0]
|
||||
success_job = image_studio.create_job(
|
||||
project.id,
|
||||
source_asset_id=source.id,
|
||||
job_type="白底图",
|
||||
prompt="成功图",
|
||||
path=config["db_path"],
|
||||
)
|
||||
success_job = image_studio.update_job_status(
|
||||
success_job.id,
|
||||
"succeeded",
|
||||
path=config["db_path"],
|
||||
)
|
||||
failed_job = image_studio.create_job(
|
||||
project.id,
|
||||
source_asset_id=source.id,
|
||||
job_type="场景图",
|
||||
prompt="失败图",
|
||||
path=config["db_path"],
|
||||
)
|
||||
failed_job = image_studio.update_job_status(
|
||||
failed_job.id,
|
||||
"failed",
|
||||
path=config["db_path"],
|
||||
)
|
||||
retry_job = image_studio.create_job(
|
||||
project.id,
|
||||
source_asset_id=source.id,
|
||||
job_type="场景图",
|
||||
prompt="重试图",
|
||||
path=config["db_path"],
|
||||
)
|
||||
|
||||
tab = ProductSuiteTab(config=config, db_path=config["db_path"])
|
||||
self.addCleanup(tab.close)
|
||||
state = tab._displayed_state
|
||||
state.account_alias = "alias-a"
|
||||
state.item_id = project.item_id
|
||||
state.project_id = project.id
|
||||
state.project_binding_state = project.binding_state
|
||||
state.current_job_ids = [success_job.id, failed_job.id]
|
||||
state.generation_mode = "retry"
|
||||
state.generation_retry_job_id = failed_job.id
|
||||
state.total = 1
|
||||
|
||||
tab._set_generation_job_ids(state, [retry_job.id])
|
||||
|
||||
self.assertEqual(
|
||||
[success_job.id, retry_job.id],
|
||||
state.current_job_ids,
|
||||
)
|
||||
self.assertEqual([retry_job.id], state.generation_job_ids)
|
||||
snapshot = tab._generation_job_snapshot(state)
|
||||
self.assertEqual(1, snapshot["job_ids"])
|
||||
self.assertEqual(1, snapshot["active"])
|
||||
|
||||
image_studio.update_job_status(
|
||||
retry_job.id,
|
||||
"failed",
|
||||
error="重试仍失败",
|
||||
path=config["db_path"],
|
||||
)
|
||||
state.worker = mock.Mock()
|
||||
state.thread = mock.Mock()
|
||||
state.generation_run_token = "retry-failed"
|
||||
state.started_at = time.monotonic()
|
||||
tab._generation_run_states["retry-failed"] = state.key
|
||||
messages = []
|
||||
tab._message = lambda title, message, **kwargs: messages.append(
|
||||
(title, message)
|
||||
)
|
||||
|
||||
self.assertTrue(
|
||||
tab._finalize_generation(
|
||||
state,
|
||||
"retry-failed",
|
||||
{"total": 1, "success": 0, "failed": 1},
|
||||
source="worker",
|
||||
)
|
||||
)
|
||||
self.assertEqual("图片重试失败", messages[-1][0])
|
||||
retry_cards = [
|
||||
card
|
||||
for card in tab.findChildren(SuiteResultCard)
|
||||
if card.job.id == retry_job.id
|
||||
]
|
||||
self.assertEqual(1, len(retry_cards))
|
||||
self.assertTrue(
|
||||
any(
|
||||
button.text() == "重试"
|
||||
for button in retry_cards[0].findChildren(QPushButton)
|
||||
)
|
||||
)
|
||||
|
||||
state.show_history = True
|
||||
tab._load_state(state)
|
||||
with mock.patch.object(
|
||||
tab,
|
||||
"_start_thread",
|
||||
return_value=mock.Mock(),
|
||||
):
|
||||
self.assertTrue(
|
||||
tab.start_generation(
|
||||
state,
|
||||
specs=[
|
||||
{
|
||||
"source_asset_id": source.id,
|
||||
"job_type": failed_job.job_type,
|
||||
"prompt": failed_job.prompt,
|
||||
}
|
||||
],
|
||||
retry_job_id=failed_job.id,
|
||||
)
|
||||
)
|
||||
self.assertTrue(state.show_history)
|
||||
self.assertTrue(tab.history_button.isChecked())
|
||||
state.worker = None
|
||||
state.thread = None
|
||||
state.generation_run_token = ""
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_original_list_expands_without_internal_scrollbars(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
config = self._config(temp_dir)
|
||||
|
||||
Reference in New Issue
Block a user