feat: 增强任务进度透明化

This commit is contained in:
meijiali
2026-07-03 18:14:58 +08:00
parent 61bcb2ef86
commit 9244f360be
5 changed files with 191 additions and 3 deletions
+7
View File
@@ -34,6 +34,13 @@ class TaskResponse(BaseModel):
current_stage: str | None = None
current_stage_label: str | None = None
last_progress_at: datetime | None = None
running_seconds: int = 0
seconds_since_last_progress: int | None = None
running_duration_label: str = "刚刚"
last_progress_ago_label: str = "暂无记录"
is_progress_stale: bool = False
stale_threshold_minutes: int = 10
hotspots_count: int = 0
comments_count: int = 0
reports_count: int = 0
is_demo: bool = False
+45
View File
@@ -2,6 +2,7 @@ import inspect
import json
from concurrent.futures import ThreadPoolExecutor
from collections.abc import Callable
from datetime import UTC, datetime
from sqlalchemy import func, select
from sqlalchemy.orm import Session, sessionmaker
@@ -19,6 +20,7 @@ from app.services.report_service import generate_hotspot_report, generate_item_r
RUNNING_TASK_MESSAGE = "当前有正在运行的任务,请稍后再试"
RESTART_ERROR_MESSAGE = "系统重启,任务被中断"
STALE_PROGRESS_THRESHOLD_SECONDS = 10 * 60
task_executor = ThreadPoolExecutor(max_workers=1)
@@ -148,6 +150,7 @@ def get_task(session: Session, task_id: str) -> Task | None:
def hydrate_task_progress(session: Session, task: Task) -> Task:
task.hotspots_count = session.scalar(select(func.count(Hotspot.id)).where(Hotspot.task_id == task.id)) or 0
task.comments_count = session.scalar(select(func.count(Comment.id)).where(Comment.task_id == task.id)) or 0
task.reports_count = session.scalar(select(func.count(Report.id)).where(Report.task_id == task.id)) or 0
task.is_demo = task.id.startswith("demo-")
@@ -160,9 +163,51 @@ def hydrate_task_progress(session: Session, task: Task) -> Task:
elif not stage_code and task.status == "running":
stage_code = "queued"
task.current_stage_label = STAGE_LABELS.get(stage_code or "", stage_code or "等待启动")
now = ensure_utc_datetime(utc_now())
running_start = ensure_utc_datetime(task.started_at or task.created_at)
running_end = ensure_utc_datetime(task.finished_at) or now
last_progress_at = ensure_utc_datetime(task.last_progress_at)
running_seconds = max(0, int((running_end - running_start).total_seconds()))
seconds_since_progress = max(0, int((now - last_progress_at).total_seconds())) if last_progress_at else None
task.running_seconds = running_seconds
task.seconds_since_last_progress = seconds_since_progress
task.running_duration_label = format_duration_zh(running_seconds)
task.last_progress_ago_label = f"{format_duration_zh(seconds_since_progress)}" if seconds_since_progress is not None else "暂无记录"
task.stale_threshold_minutes = STALE_PROGRESS_THRESHOLD_SECONDS // 60
task.is_progress_stale = (
task.status == "running"
and seconds_since_progress is not None
and seconds_since_progress >= STALE_PROGRESS_THRESHOLD_SECONDS
)
return task
def format_duration_zh(total_seconds: int | None) -> str:
if total_seconds is None:
return "暂无记录"
total_seconds = max(0, int(total_seconds))
minutes = total_seconds // 60
hours = minutes // 60
days = hours // 24
if days > 0:
remaining_hours = hours % 24
return f"{days}{remaining_hours}小时" if remaining_hours else f"{days}"
if hours > 0:
remaining_minutes = minutes % 60
return f"{hours}小时{remaining_minutes}分钟" if remaining_minutes else f"{hours}小时"
if minutes > 0:
return f"{minutes}分钟"
return "刚刚"
def ensure_utc_datetime(value: datetime | None) -> datetime | None:
if value is None:
return None
if value.tzinfo is None:
return value.replace(tzinfo=UTC)
return value.astimezone(UTC)
def update_task_stage(task: Task, stage: str) -> None:
task.current_stage = stage
task.last_progress_at = utc_now()
+13 -3
View File
@@ -42,17 +42,27 @@
<div class="metric-box">
<div class="text-muted small">实际结果</div>
<strong>实际评论 {{ task.comments_count or 0 }}</strong>
<div class="text-muted small">报告 {{ task.reports_count or 0 }} / 内容 {{ task.total_items_count }}</div>
<div class="text-muted small">已获取热点 {{ task.hotspots_count or 0 }} / 目标 {{ task.hotspot_limit }} · 报告 {{ task.reports_count or 0 }}</div>
</div>
</div>
<div class="col-md-4">
<div class="metric-box">
<div class="text-muted small">最近进度</div>
<strong>{{ task.last_progress_at or task.created_at }}</strong>
<div class="text-muted small">外部接口和 AI 响应较慢时,阶段可能短时间不变</div>
<strong>{{ task.last_progress_ago_label or "暂无记录" }}</strong>
<div class="text-muted small">运行时长 {{ task.running_duration_label or "刚刚" }} · 评论 {{ task.comments_count or 0 }}</div>
</div>
</div>
</div>
{% if task.status == "running" %}
<div class="alert alert-secondary">
小规模通常较快,默认规模可能需要数分钟,取决于 TikHub 与 AI 响应速度。
</div>
{% endif %}
{% if task.is_progress_stale %}
<div class="alert alert-warning">
超过 {{ task.stale_threshold_minutes }} 分钟没有进度更新,可能仍在等待外部接口或 AI 响应;如果长时间不恢复,请刷新状态或查看后端日志。
</div>
{% endif %}
{% if task.status == "success" and (task.comments_count or 0) < target_comments %}
<div class="alert alert-info">少于理论上限通常是内容本身评论不足或平台返回不足,不直接代表任务失败。若失败内容数大于 0,请结合失败原因判断。</div>
{% endif %}