Skip to content

Commit b1730e8

Browse files
committed
feat: 后处理阶段进度反馈(GUI + CLI)
- GUI: postprocessing 状态正确传递,前端显示"正在后处理: 币圈合成数据..." - GUI: elapsed_seconds 在后处理阶段持续更新 - GUI: 防重入判断(start_sync/health_check/repair)统一纳入 postprocessing - GUI: done/error 终态显式清空 postprocessing 状态 - CLI: update/all_data 加 progress_callback,后处理阶段打印提示 - dry_run 模式不发 postprocessing 通知(预处理实际跳过)
1 parent 31e0052 commit b1730e8

5 files changed

Lines changed: 38 additions & 16 deletions

File tree

quantclass_sync_internal/cli.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,14 @@ def cmd_setup(
472472
secrets_file=str(secrets_path),
473473
)
474474

475+
def _cli_sync_progress(product_name: str, completed: int, total: int, *,
476+
status: str = "ok", **kwargs) -> None:
477+
"""CLI 同步进度回调:后处理阶段打印提示,避免长时间无输出。"""
478+
if status == "postprocessing":
479+
detail = kwargs.get("postprocess_detail", "")
480+
msg = f"正在后处理: {detail}..." if detail else "正在后处理数据..."
481+
RICH_CONSOLE.print(f" {msg}")
482+
475483
@app.command("update")
476484
@command_guard("update")
477485
def cmd_update(
@@ -566,6 +574,7 @@ def cmd_update(
566574
api_call_limit=user_config.api_call_limit,
567575
course_type=user_config.course_type,
568576
auto_confirm=yes,
577+
progress_callback=_cli_sync_progress,
569578
)
570579
log_info("update 执行完成。", event="CMD_DONE", exit_code=exit_code)
571580
if exit_code == -1:
@@ -843,6 +852,7 @@ def cmd_all_data(
843852
# 从 user_config 读取 API 调用限额和课程类型(影响确认提示文本,无配置时用默认值)
844853
api_call_limit=getattr(user_config_all, "api_call_limit", 50),
845854
course_type=getattr(user_config_all, "course_type", ""),
855+
progress_callback=_cli_sync_progress,
846856
)
847857
log_info("all_data 执行完成。", event="CMD_DONE", exit_code=exit_code)
848858
if exit_code == -1:

quantclass_sync_internal/gui/api.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def _format_run_summary(raw_run: Dict[str, Any]) -> Dict[str, Any]:
6969

7070
# _progress 的初始结构,每次 start_sync 前重置为此形态
7171
_PROGRESS_INIT: Dict[str, Any] = {
72-
"status": "idle", # idle / syncing / confirm_needed / done / error
72+
"status": "idle", # idle / syncing / confirm_needed / postprocessing / done / error
7373
"current_product": "", # 最近完成的产品名
7474
"completed": 0, # 已完成产品数
7575
"total": 0, # 本次同步产品总数
@@ -79,6 +79,7 @@ def _format_run_summary(raw_run: Dict[str, Any]) -> Dict[str, Any]:
7979
"products": [], # 已完成产品列表 [{name, status, elapsed_seconds, files_count}]
8080
"all_products": [], # 全部产品名列表(由 progress_callback 初始化调用时传入)
8181
"estimate": None, # EstimateResult 的 dict 表示(confirm_needed 时填充)
82+
"postprocess_detail": "", # 后处理阶段描述(用户可读)
8283
}
8384

8485

@@ -432,8 +433,8 @@ def start_sync(self, retry_failed: bool = False) -> Dict[str, Any]:
432433
# 检查 worker 线程是否仍在运行(cancel 后 status 变 error,但线程可能未退出)
433434
if hasattr(self, "_sync_thread") and self._sync_thread and self._sync_thread.is_alive():
434435
return {"started": False, "message": "同步正在进行中,请等待完成后再试。"}
435-
# confirm_needed 状态表示同步已在进行中(等待用户确认),也需拦截
436-
if self._progress.get("status") in ("syncing", "confirm_needed"):
436+
# confirm_needed/postprocessing 状态表示同步已在进行中,也需拦截
437+
if self._progress.get("status") in ("syncing", "confirm_needed", "postprocessing"):
437438
return {"started": False, "message": "同步正在进行中,请等待完成后再试。"}
438439

439440
# retry_failed 分支:从上次 run_summary 读取失败产品名
@@ -552,7 +553,7 @@ def open_data_dir(self) -> Dict[str, Any]:
552553
def start_health_check(self) -> Dict[str, Any]:
553554
"""启动后台健康检查线程。同步中(含等待确认)拒绝,重复启动拒绝。"""
554555
with self._lock:
555-
if self._progress.get("status") in ("syncing", "confirm_needed"):
556+
if self._progress.get("status") in ("syncing", "confirm_needed", "postprocessing"):
556557
return {"ok": False, "error": "同步进行中,请稍后再试"}
557558
if self._health_progress["checking"]:
558559
return {"ok": False, "error": "检查已在进行中"}
@@ -606,9 +607,9 @@ def get_health_result(self) -> Dict[str, Any]:
606607
return self._health_progress.get("result")
607608

608609
def repair_health_issues(self) -> Dict[str, Any]:
609-
"""修复可修复的数据问题。同步中(含等待确认)拒绝。"""
610+
"""修复可修复的数据问题。同步中(含等待确认/后处理)拒绝。"""
610611
with self._lock:
611-
if self._progress.get("status") in ("syncing", "confirm_needed"):
612+
if self._progress.get("status") in ("syncing", "confirm_needed", "postprocessing"):
612613
return {"ok": False, "error": "同步进行中,请稍后修复"}
613614
result = self._health_progress.get("result")
614615
if not result or not result.get("ok"):
@@ -866,6 +867,12 @@ def progress_callback(product_name: str, completed: int, total: int, *,
866867
if status == "init":
867868
self._progress["total"] = total
868869
return
870+
# 后处理阶段:更新顶层 status 和 elapsed,供前端检测
871+
if status == "postprocessing":
872+
self._progress["status"] = "postprocessing"
873+
self._progress["elapsed_seconds"] = round(time.time() - t_start, 1)
874+
self._progress["postprocess_detail"] = _kwargs.get("postprocess_detail", "")
875+
return
869876
# 计算本产品同步的文件数(新建 + 更新)
870877
files_count = (stats.created_files + stats.updated_files) if stats else 0
871878
# 追加到已完成产品列表,包含 error 字段供前端展示失败原因

quantclass_sync_internal/gui/assets/app.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ document.addEventListener('alpine:init', () => {
6060
allProducts: [], // 全部待同步产品名(用于计算等待中列表)
6161
showWaiting: false, // 等待中产品列表是否展开
6262
postprocessing: false, // 后处理阶段标志
63+
postprocessDetail: '', // 后处理描述(用户可读)
6364
estimateData: null, // API 调用量预估数据(confirm_needed 时填充,用于展示确认卡片)
6465

6566
// ===== 初始化 =====
@@ -181,6 +182,7 @@ document.addEventListener('alpine:init', () => {
181182
this.allProducts = [];
182183
this.showWaiting = false;
183184
this.postprocessing = false;
185+
this.postprocessDetail = '';
184186
this.estimateData = null;
185187
try {
186188
const result = await window.pywebview.api.start_sync();
@@ -213,6 +215,7 @@ document.addEventListener('alpine:init', () => {
213215
this.allProducts = [];
214216
this.showWaiting = false;
215217
this.postprocessing = false;
218+
this.postprocessDetail = '';
216219
this.estimateData = null;
217220
try {
218221
// true 表示仅重试失败产品
@@ -264,8 +267,11 @@ document.addEventListener('alpine:init', () => {
264267
// 不切换 syncStatus,继续轮询等待用户点击确认/取消
265268
} else if (p.status === 'postprocessing') {
266269
this.postprocessing = true;
270+
this.postprocessDetail = p.postprocess_detail || '';
267271
} else if (p.status === 'done') {
268272
this.syncStatus = 'done';
273+
this.postprocessing = false;
274+
this.postprocessDetail = '';
269275
this.estimateData = null;
270276
this.runSummary = p.run_summary;
271277
this.historyLoaded = false; // 有新运行,下次切历史页时刷新
@@ -274,6 +280,8 @@ document.addEventListener('alpine:init', () => {
274280
return; // 终态,不再调度下次轮询
275281
} else if (p.status === 'error') {
276282
this.syncStatus = 'error';
283+
this.postprocessing = false;
284+
this.postprocessDetail = '';
277285
this.estimateData = null;
278286
this.errorMessage = p.error_message || '同步失败';
279287
this.runSummary = p.run_summary; // 部分失败时也携带摘要

quantclass_sync_internal/gui/assets/index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -390,8 +390,8 @@ <h2 class="setup-title">QuantClass Sync 初始设置</h2>
390390
<span class="qs-progress-text" x-text="total > 0 ? completed + ' / ' + total : '准备中...'"></span>
391391
</div>
392392
<!-- 进度指示行 -->
393-
<div x-show="postprocessing" style="font-size:0.92em; color:#6b7280; margin:6px 0 2px;">
394-
正在后处理数据...
393+
<div x-show="postprocessing" style="font-size:0.92em; color:#6b7280; margin:6px 0 2px;"
394+
x-text="'正在后处理' + (postprocessDetail ? ': ' + postprocessDetail : '') + '...'">
395395
</div>
396396
<div x-show="total > 0 && !postprocessing" style="font-size:0.92em; color:#6b7280; margin:6px 0 2px;">
397397
<span x-text="'已完成 ' + completed + '/' + total"></span>

quantclass_sync_internal/orchestrator.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1565,12 +1565,7 @@ def run_update_with_settings(
15651565
max_workers=max_workers,
15661566
progress_callback=progress_callback,
15671567
)
1568-
# 后处理前通知前端,postprocessing 状态用于 GUI 显示"正在后处理数据..."
1569-
if progress_callback is not None:
1570-
try:
1571-
progress_callback("", len(plans), len(plans), status="postprocessing")
1572-
except Exception:
1573-
pass
1568+
# dry_run 模式下 _maybe_run_coin_preprocess 会直接跳过,不发 postprocessing 通知
15741569
preprocess_has_error = _maybe_run_coin_preprocess(
15751570
command_ctx=command_ctx,
15761571
report=report,
@@ -1591,10 +1586,12 @@ def run_update_with_settings(
15911586
max_workers=max_workers,
15921587
progress_callback=progress_callback,
15931588
)
1594-
# 后处理前通知前端,postprocessing 状态用于 GUI 显示"正在后处理数据..."
1589+
# 后处理前通知前端,postprocessing 状态用于 GUI 显示后处理进度
15951590
if progress_callback is not None:
15961591
try:
1597-
progress_callback("", len(plans), len(plans), status="postprocessing")
1592+
progress_callback("", len(plans), len(plans),
1593+
status="postprocessing",
1594+
postprocess_detail="币圈合成数据")
15981595
except Exception:
15991596
pass
16001597
preprocess_has_error = _maybe_run_coin_preprocess(

0 commit comments

Comments
 (0)