Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions DEVELOPMENT_LOG.md
Original file line number Diff line number Diff line change
@@ -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、转写细分进度、最新运行日志、候选/输出数量和发布任务汇总。
Expand Down
7 changes: 7 additions & 0 deletions NEXT_STEPS.md
Original file line number Diff line number Diff line change
@@ -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%`;实际数值会随后续排期发送继续变化。
Expand Down
56 changes: 27 additions & 29 deletions app/services/task_query_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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",
},
],
}

Expand Down
4 changes: 4 additions & 0 deletions app/static/css/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 3 additions & 3 deletions app/templates/clips_overview.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@
<div>
<p class="eyebrow">Clip Review</p>
<h1>片段审核工作台</h1>
<p>按工作流查看每个任务是否已经进入 AI 分析、人工审核、切片生成或异常排查阶段。</p>
<p>集中查看累计审核任务、通过视频和已完成任务,并继续进入单任务审核。</p>
</div>
<a class="primary-button" href="/tasks/new">新建任务</a>
</section>

<section class="stat-grid review-stat-grid" aria-label="片段审核统计">
<section class="stat-grid review-stat-grid clip-review-stat-grid" aria-label="片段审核统计">
{% for item in stats %}
<article class="stat-card tone-{{ item.tone }}">
<div class="stat-icon"></div>
<span>{{ item.label }}</span>
<strong>{{ item.value }}</strong>
<small>来自当前任务库</small>
<small>{{ item.note }}</small>
</article>
{% endfor %}
</section>
Expand Down
7 changes: 7 additions & 0 deletions docs/UI_REFERENCE.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# UI 参考说明

## 2026-08-26 更新:片段审核累计统计

- 片段审核工作台顶部只保留三项结果指标:“累计审核任务、已通过视频、已完成任务”,移除待 AI、待检查、可生成切片和异常任务等过程型统计。
- “累计审核任务”表示当前任务库中已经生成候选片段并进入审核流程的未删除任务;“已通过视频”表示这些任务下当前启用且未删除的视频片段;“已完成任务”兼容手动、全自动和部分完成状态,任务行徽标也按同一大小写规范化口径显示。
- 每张卡片显示自己的业务说明,不再统一写“来自当前任务库”,避免把自动启用片段误解为全部人工确认。
- 桌面端三张卡片等宽排列;1180px 及以下沿用现有单列响应式规则。该布局使用片段审核专属类,不影响字幕工作台的统计卡片。

## 2026-08-26 更新:任务详情实时进度同步

- 状态概览新增“总流程进度”和“当前操作”两层信息:总进度保留全流程位置,当前操作显示转写、AI、切片或托管发布的独立百分比和最新说明。
Expand Down
57 changes: 57 additions & 0 deletions docs/agent_tasks/2026-08-26-clip-review-summary.md
Original file line number Diff line number Diff line change
@@ -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、切片或平台发布验证。
51 changes: 44 additions & 7 deletions tests/test_task_query_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
"""有任务和候选片段时统计字段正确"""
Expand Down Expand Up @@ -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]
Expand All @@ -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 ──────────────────────────────────────
Expand Down