diff --git a/DEVELOPMENT_LOG.md b/DEVELOPMENT_LOG.md
index e0375c9..fbbe5dc 100644
--- a/DEVELOPMENT_LOG.md
+++ b/DEVELOPMENT_LOG.md
@@ -1,5 +1,14 @@
# Development Log
+## 2026-08-26 片段审核累计统计精简
+
+- `/clips` 顶部统计从“待 AI 分析、待检查、可生成切片、已完成、异常任务”五项过程指标,精简为“累计审核任务、已通过视频、已完成任务”三项累计结果。
+- “累计审核任务”按当前未删除任务中已有未删除候选片段的任务数计算,表示已经进入候选片段审核流程;不把自动流程写入的 `reviewed` 字段误称为纯人工审核。
+- “已通过视频”按当前未删除任务下启用且未删除的候选片段数计算,卡片说明明确为“当前启用的视频片段”。
+- “已完成任务”兼容 `completed`、`completed_with_errors` 和历史大写 `COMPLETED`,并排除已删除任务;审核队列的状态徽标使用同一套规范化口径,不再把历史大写完成状态误显示为“待检查”。
+- 三项统计使用独立三列布局,不改变字幕工作台继续复用的五列统计样式;窄屏仍按现有响应式规则切换为单列。
+- 当前活动库按新口径显示 `20 / 163 / 20`;定向统计与性能回归 `35 passed`、完整回归 `793 passed`,Python Compileall、Ruff 和 `git diff --check` 均通过,未触发真实 AI、切片或平台发布。
+
## 2026-08-26 任务详情实时进度与日志统一
- `/api/tasks/{task_id}/live-status` 扩展为任务详情唯一实时快照:同一次响应返回主任务状态、活动 Workflow Job、转写细分进度、最新运行日志、候选/输出数量和发布任务汇总。
diff --git a/NEXT_STEPS.md b/NEXT_STEPS.md
index 0c4191d..6e997ed 100644
--- a/NEXT_STEPS.md
+++ b/NEXT_STEPS.md
@@ -1,5 +1,12 @@
# Next Steps
+## 2026-08-26 片段审核累计统计检查
+
+1. 打开 `http://127.0.0.1:8001/clips`,顶部应只显示“累计审核任务、已通过视频、已完成任务”三张卡片,不再出现待 AI、待检查、可生成切片和异常任务统计。
+2. 当前活动库预期显示累计审核任务 `20`、已通过视频 `163`、已完成任务 `20`;以后新增、启用、停用或删除任务/候选片段后,数值会按当前未删除数据重新计算。
+3. “已通过视频”指当前处于启用状态的视频片段,可能来自人工审核或全自动选片,不应理解为全部都经过人工逐条点击确认。
+4. 本轮只调整查询统计与展示,不需要运行 AI、重新切片或点击发送中心,也不会触发抖音/B站投稿。
+
## 2026-08-26 任务详情实时进度检查
1. 打开 `http://127.0.0.1:8001/tasks/3210d91ee1fb`,状态概览应显示“托管发布中 · 9/12”,并分别显示总流程 `97%`、当前操作“托管发布” `75%`;实际数值会随后续排期发送继续变化。
diff --git a/app/services/task_query_service.py b/app/services/task_query_service.py
index 322aff3..a3f2a2a 100644
--- a/app/services/task_query_service.py
+++ b/app/services/task_query_service.py
@@ -320,19 +320,20 @@ def get_clips_overview_context() -> dict:
enabled_count = counts["enabled"]
review_ready = clip_count > 0
can_cut = enabled_count > 0 and task["source_exists"]
- if task["status"] == TaskStatus.failed.value:
+ normalized_status = str(task.get("status") or "").lower()
+ if normalized_status == TaskStatus.failed.value:
review_stage = "异常"
review_tone = "red"
- elif task["status"] == TaskStatus.completed.value:
+ elif normalized_status == TaskStatus.completed.value:
review_stage = "已完成"
review_tone = "green"
- elif task["status"] == TaskStatus.completed_with_errors.value:
+ elif normalized_status == TaskStatus.completed_with_errors.value:
review_stage = "部分完成"
review_tone = "amber"
- elif task["status"] == TaskStatus.pending_review.value or review_ready:
+ elif normalized_status == TaskStatus.pending_review.value or review_ready:
review_stage = "待检查"
review_tone = "purple"
- elif task["status"] in {TaskStatus.pending_ai.value, TaskStatus.ai_analyzing.value}:
+ elif normalized_status in {TaskStatus.pending_ai.value, TaskStatus.ai_analyzing.value}:
review_stage = "待 AI"
review_tone = "blue"
else:
@@ -351,42 +352,39 @@ def get_clips_overview_context() -> dict:
}
)
+ completed_statuses = {
+ TaskStatus.completed.value,
+ TaskStatus.completed_with_errors.value,
+ }
+ reviewed_task_count = sum(1 for task in enriched_tasks if task["real_clip_count"] > 0)
+ passed_clip_count = sum(task["enabled_clip_count"] for task in enriched_tasks)
+ completed_task_count = sum(
+ 1
+ for task in tasks
+ if str(task.get("status") or "").lower() in completed_statuses
+ )
+
return {
"tasks": enriched_tasks,
"stats": [
{
- "label": "待 AI 分析",
- "value": sum(
- 1
- for task in tasks
- if task["status"] in {TaskStatus.pending_ai.value, TaskStatus.ai_analyzing.value}
- ),
+ "label": "累计审核任务",
+ "value": reviewed_task_count,
+ "note": "已进入候选片段审核流程",
"tone": "blue",
},
{
- "label": "待检查",
- "value": sum(1 for task in enriched_tasks if task["review_stage"] == "待检查"),
- "tone": "purple",
- },
- {
- "label": "可生成切片",
- "value": sum(1 for task in enriched_tasks if task["can_cut"]),
+ "label": "已通过视频",
+ "value": passed_clip_count,
+ "note": "当前启用的视频片段",
"tone": "green",
},
{
- "label": "已完成",
- "value": sum(
- 1
- for task in tasks
- if task["status"] in {TaskStatus.completed.value, TaskStatus.completed_with_errors.value}
- ),
+ "label": "已完成任务",
+ "value": completed_task_count,
+ "note": "含手动与全自动完成状态",
"tone": "green",
},
- {
- "label": "异常任务",
- "value": sum(1 for task in tasks if task["status"] == TaskStatus.failed.value),
- "tone": "red",
- },
],
}
diff --git a/app/static/css/styles.css b/app/static/css/styles.css
index d2b68fe..6174240 100644
--- a/app/static/css/styles.css
+++ b/app/static/css/styles.css
@@ -1809,6 +1809,10 @@ button:disabled {
grid-template-columns: repeat(5, minmax(150px, 1fr));
}
+.clip-review-stat-grid {
+ grid-template-columns: repeat(3, minmax(180px, 1fr));
+}
+
.review-board {
display: grid;
gap: 12px;
diff --git a/app/templates/clips_overview.html b/app/templates/clips_overview.html
index f00539e..03a4d80 100644
--- a/app/templates/clips_overview.html
+++ b/app/templates/clips_overview.html
@@ -6,18 +6,18 @@
Clip Review
片段审核工作台
-
按工作流查看每个任务是否已经进入 AI 分析、人工审核、切片生成或异常排查阶段。
+
集中查看累计审核任务、通过视频和已完成任务,并继续进入单任务审核。
新建任务
-
+
{% for item in stats %}
{{ item.label }}
{{ item.value }}
- 来自当前任务库
+ {{ item.note }}
{% endfor %}
diff --git a/docs/UI_REFERENCE.md b/docs/UI_REFERENCE.md
index ea822b4..e6eef52 100644
--- a/docs/UI_REFERENCE.md
+++ b/docs/UI_REFERENCE.md
@@ -1,5 +1,12 @@
# UI 参考说明
+## 2026-08-26 更新:片段审核累计统计
+
+- 片段审核工作台顶部只保留三项结果指标:“累计审核任务、已通过视频、已完成任务”,移除待 AI、待检查、可生成切片和异常任务等过程型统计。
+- “累计审核任务”表示当前任务库中已经生成候选片段并进入审核流程的未删除任务;“已通过视频”表示这些任务下当前启用且未删除的视频片段;“已完成任务”兼容手动、全自动和部分完成状态,任务行徽标也按同一大小写规范化口径显示。
+- 每张卡片显示自己的业务说明,不再统一写“来自当前任务库”,避免把自动启用片段误解为全部人工确认。
+- 桌面端三张卡片等宽排列;1180px 及以下沿用现有单列响应式规则。该布局使用片段审核专属类,不影响字幕工作台的统计卡片。
+
## 2026-08-26 更新:任务详情实时进度同步
- 状态概览新增“总流程进度”和“当前操作”两层信息:总进度保留全流程位置,当前操作显示转写、AI、切片或托管发布的独立百分比和最新说明。
diff --git a/docs/agent_tasks/2026-08-26-clip-review-summary.md b/docs/agent_tasks/2026-08-26-clip-review-summary.md
new file mode 100644
index 0000000..4fff001
--- /dev/null
+++ b/docs/agent_tasks/2026-08-26-clip-review-summary.md
@@ -0,0 +1,57 @@
+# 片段审核累计统计精简
+
+## 背景
+
+`/clips` 顶部当前显示“待 AI 分析、待检查、可生成切片、已完成、异常任务”五项阶段统计。用户希望只保留累计审核任务、已通过视频和已完成任务三个长期有用的汇总指标。
+
+## 目标
+
+- 将顶部统计精简为三项:累计审核任务、已通过视频、已完成任务。
+- 每项使用明确、可验证且排除软删除数据的 SQLite 口径。
+- 保持审核队列、单任务指标和操作入口不变。
+
+## 允许修改范围
+
+- `app/services/task_query_service.py`
+- `app/templates/clips_overview.html`
+- `app/static/css/styles.css`
+- `tests/test_task_query_service.py`
+- `tests/test_p1_1_db_performance.py`(仅相关回归需要时)
+- `DEVELOPMENT_LOG.md`、`NEXT_STEPS.md`、`docs/UI_REFERENCE.md`
+
+## 禁止修改范围
+
+- 任务、候选片段、切片和发布状态机。
+- SQLite Schema、迁移和真实数据。
+- AI Provider、Prompt、账号、Cookie、Token、`.env` 和认证配置。
+- 真实 AI、切片、字幕或平台发布流程。
+
+## 已确定实现要求
+
+- “累计审核任务”统计当前未删除任务中至少有一条未删除候选片段的去重任务数;当前数据库没有可靠的任务级人工审核事件,不能把 `reviewed=1` 冒充纯人工审核。
+- “已通过视频”统计上述未删除任务中 `is_deleted=0 AND enabled=1` 的候选片段总数,页面说明为“当前启用的视频片段”。
+- “已完成任务”兼容 `completed / completed_with_errors / COMPLETED`,统一大小写后统计当前未删除任务。
+- 三张卡分别提供自己的统计说明,不再统一显示“来自当前任务库”。
+- 统计网格由五列改为三列;现有响应式折叠逻辑继续保留。
+
+## 验收标准
+
+- `/clips` 顶部只显示“累计审核任务、已通过视频、已完成任务”。
+- 当前活动库预期显示 `20 / 163 / 20`;数值随数据变化时以同一口径实时渲染。
+- 软删除任务和软删除候选片段不计入累计审核与通过数。
+- 大写自动完成状态 `COMPLETED` 不再被漏算。
+- 审核队列结构、任务行指标、详情和进入审核按钮保持不变。
+- 桌面与 390px 窄屏无横向溢出。
+
+## 测试命令
+
+- `.venv\Scripts\python.exe -m pytest tests/test_task_query_service.py tests/test_p1_1_db_performance.py -q`
+- `.venv\Scripts\python.exe -m pytest -q`
+- `.venv\Scripts\python.exe -m compileall -q app tests`
+- `.venv\Scripts\python.exe -m ruff check app/services/task_query_service.py tests/test_task_query_service.py`
+- `git diff --check`
+- 浏览器刷新 `http://127.0.0.1:8001/clips`,核对三项指标、审核队列、窄屏布局和页面错误。
+
+## 返回格式
+
+报告统计口径、修改文件、测试与浏览器证据、分支、提交、推送和 PR;不得把本地统计验证描述为真实 AI、切片或平台发布验证。
diff --git a/tests/test_task_query_service.py b/tests/test_task_query_service.py
index f96fc08..2228c69 100644
--- a/tests/test_task_query_service.py
+++ b/tests/test_task_query_service.py
@@ -399,8 +399,9 @@ def test_empty_clips_overview_fields_complete(self):
assert context["tasks"] == []
stat_labels = {s["label"] for s in context["stats"]}
- expected_labels = {"待 AI 分析", "待检查", "可生成切片", "已完成", "异常任务"}
+ expected_labels = {"累计审核任务", "已通过视频", "已完成任务"}
assert stat_labels == expected_labels
+ assert all(stat["value"] == 0 for stat in context["stats"])
def test_clips_overview_with_tasks_and_clips(self):
"""有任务和候选片段时统计字段正确"""
@@ -433,6 +434,12 @@ def test_clips_overview_with_tasks_and_clips(self):
assert task["review_ready"] is True
assert task["can_cut"] is False # source_exists 为 False
+ stat_map = {stat["label"]: stat for stat in context["stats"]}
+ assert stat_map["累计审核任务"]["value"] == 1
+ assert stat_map["已通过视频"]["value"] == 2
+ assert stat_map["已通过视频"]["note"] == "当前启用的视频片段"
+ assert stat_map["已完成任务"]["value"] == 0
+
def test_clips_overview_with_deleted_clips(self):
"""已删除的候选片段不计入统计"""
task_id = uuid4().hex[:12]
@@ -449,20 +456,50 @@ def test_clips_overview_with_deleted_clips(self):
assert task["enabled_clip_count"] == 1
def test_clips_overview_stats_correct(self):
- """统计卡片数值正确"""
+ """累计审核、启用片段和完成任务统计正确,且排除已删除数据"""
task_id_1 = uuid4().hex[:12]
task_id_2 = uuid4().hex[:12]
+ task_id_3 = uuid4().hex[:12]
+ task_id_4 = uuid4().hex[:12]
today = datetime.now(timezone.utc).isoformat(timespec="seconds")
- _insert_test_task(task_id_1, "待AI", status="pending_ai", created_at=today)
- _insert_test_task(task_id_2, "失败", status="failed", created_at=today)
+ _insert_test_task(task_id_1, "待检查", status="pending_review", created_at=today)
+ _insert_test_task(task_id_2, "大写完成", status="COMPLETED", created_at=today)
+ _insert_test_task(
+ task_id_3,
+ "部分完成",
+ status="completed_with_errors",
+ created_at=today,
+ )
+ _insert_test_task(
+ task_id_4,
+ "已删除任务",
+ status="completed",
+ is_deleted=1,
+ created_at=today,
+ )
+ _insert_test_clip_candidate("clip_stats_1", task_id_1, "启用片段", enabled=1)
+ _insert_test_clip_candidate("clip_stats_2", task_id_1, "未启用片段", enabled=0)
+ _insert_test_clip_candidate("clip_stats_3", task_id_2, "完成片段", enabled=1)
+ _insert_test_clip_candidate("clip_stats_4", task_id_4, "已删除任务片段", enabled=1)
+ _insert_test_clip_candidate(
+ "clip_stats_5",
+ task_id_1,
+ "已删除片段",
+ enabled=1,
+ is_deleted=1,
+ )
context = get_clips_overview_context()
stat_map = {s["label"]: s["value"] for s in context["stats"]}
+ task_map = {task["id"]: task for task in context["tasks"]}
- assert stat_map["待 AI 分析"] == 1
- assert stat_map["待检查"] == 0
- assert stat_map["异常任务"] == 1
+ assert stat_map == {
+ "累计审核任务": 2,
+ "已通过视频": 2,
+ "已完成任务": 2,
+ }
+ assert task_map[task_id_2]["review_stage"] == "已完成"
# ── Subtitle Workflow Context ──────────────────────────────────────