From 6702c61edd0307110390cd13524d4bbc6a013768 Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 26 Aug 2026 15:46:46 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=EF=BC=9A=E9=87=8D=E6=8E=92?= =?UTF-8?q?=E5=8F=91=E9=80=81=E4=B8=AD=E5=BF=83=20AI=20=E6=96=87=E6=A1=88?= =?UTF-8?q?=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DEVELOPMENT_LOG.md | 9 +++ NEXT_STEPS.md | 8 +++ app/static/css/styles.css | 47 +++++++++++++- app/static/js/publish-center.js | 33 +++------- app/templates/publish.html | 31 +++++++-- docs/TASK_FLOW.md | 2 +- docs/UI_REFERENCE.md | 13 +++- .../2026-08-26-publish-ai-action-placement.md | 64 +++++++++++++++++++ tests/test_publish_center_browser.py | 4 +- tests/test_publish_center_cleanup.py | 24 +++++++ 10 files changed, 199 insertions(+), 36 deletions(-) create mode 100644 docs/agent_tasks/2026-08-26-publish-ai-action-placement.md diff --git a/DEVELOPMENT_LOG.md b/DEVELOPMENT_LOG.md index fbbe5dc..5ce1273 100644 --- a/DEVELOPMENT_LOG.md +++ b/DEVELOPMENT_LOG.md @@ -1,5 +1,14 @@ # Development Log +## 2026-08-26 发送中心 AI 文案操作分层 + +- 明确区分“同步遗漏切片”和“AI 重写文案”:前者继续固定使用 `use_ai=false`,只为遗漏成片补建发布草稿与默认封面;后者只修改已存在草稿的标题、话题和简介。 +- 原页面顶栏“补充缺失任务”移入内容准备的维护区并改名为“同步遗漏切片”,维护区明确标注“不调用 AI、不修改已有文案”;账号管理继续留在页面顶栏。 +- 单条 AI 操作移动到发布文案字段上方并命名为“AI 重写本条文案”;勾选后的批量入口改为“AI 重写已选文案”,切换到排期计划或执行记录后隐藏。 +- 移除 `/publish` 页面加载时静默调用旧草稿全局 AI 升级的行为;后端兼容接口继续保留,但 AI 文案只在用户明确点击单条或已选批量入口后执行。 +- 发布任务、调度器、Publisher、账号、排期及执行记录状态机均未修改。 +- 定向发送中心回归 `52 passed`、完整回归 `794 passed`;Python Compileall、JavaScript 语法、Ruff 和 `git diff --check` 均通过,测试使用隔离数据与 Mock,未调用真实 AI、封面生成、排期或平台发送。 + ## 2026-08-26 片段审核累计统计精简 - `/clips` 顶部统计从“待 AI 分析、待检查、可生成切片、已完成、异常任务”五项过程指标,精简为“累计审核任务、已通过视频、已完成任务”三项累计结果。 diff --git a/NEXT_STEPS.md b/NEXT_STEPS.md index 6e997ed..57c2ffb 100644 --- a/NEXT_STEPS.md +++ b/NEXT_STEPS.md @@ -1,5 +1,13 @@ # Next Steps +## 2026-08-26 发送中心 AI 文案操作检查 + +1. 打开 `http://127.0.0.1:8001/publish`:页面顶栏只保留“账号管理”,“同步遗漏切片”和“补齐抖音缺失封面”位于内容准备的维护区。 +2. “同步遗漏切片”只为尚未进入发送中心的成片补建草稿和默认封面,不调用 AI,也不会改写已有文案;平时自动同步正常时无需点击。 +3. 需要改一条文案时,使用内容卡上方的“AI 重写本条文案”;需要改多条时先勾选,再使用底部“AI 重写已选文案”。AI 结果会立即保存到对应草稿。 +4. 排期计划只管理时间,执行记录只核对发送结果,这两个区域不再提供 AI 文案按钮。刷新页面不会自动重写旧草稿。 +5. 本轮验收不需要点击任何 AI、封面、排期或发送按钮,也不会触发真实抖音/B站投稿。 + ## 2026-08-26 片段审核累计统计检查 1. 打开 `http://127.0.0.1:8001/clips`,顶部应只显示“累计审核任务、已通过视频、已完成任务”三张卡片,不再出现待 AI、待检查、可生成切片和异常任务统计。 diff --git a/app/static/css/styles.css b/app/static/css/styles.css index 6174240..763c45b 100644 --- a/app/static/css/styles.css +++ b/app/static/css/styles.css @@ -5309,7 +5309,29 @@ td a, flex-wrap: wrap; } -.publish-content-header [data-backfill-covers][data-loading="true"] { +.publish-content-maintenance { + display: flex; + align-items: center; + justify-content: space-between; + gap: 18px; + margin-bottom: 16px; + padding: 12px 14px; + border: 1px solid rgba(126, 151, 187, 0.18); + border-radius: 14px; + background: rgba(247, 250, 255, 0.78); +} + +.publish-content-maintenance > div:first-child { + display: grid; + gap: 3px; +} + +.publish-content-maintenance small, +.publish-copy-toolbar small { + color: var(--muted); +} + +.publish-content-maintenance [data-backfill-covers][data-loading="true"] { cursor: wait; opacity: 0.72; } @@ -5414,6 +5436,23 @@ td a, flex-wrap: wrap; gap: 10px; } + +.publish-copy-toolbar { + display: flex; + align-items: center; + justify-content: space-between; + gap: 14px; + margin-bottom: 12px; + padding: 10px 12px; + border-radius: 12px; + background: rgba(238, 245, 255, 0.7); +} + +.publish-copy-toolbar > div { + display: grid; + gap: 2px; +} + .publish-schedule-badge { display: inline-flex; align-items: center; @@ -5805,6 +5844,12 @@ td a, } @media (max-width: 760px) { + .publish-content-maintenance, + .publish-copy-toolbar { + align-items: stretch; + flex-direction: column; + } + .publish-copy-toolbar .secondary-button { width: 100%; } .publish-content-header-actions { width: 100%; justify-content: stretch; diff --git a/app/static/js/publish-center.js b/app/static/js/publish-center.js index f283f11..be4ac43 100644 --- a/app/static/js/publish-center.js +++ b/app/static/js/publish-center.js @@ -100,7 +100,7 @@ if (publishCenterRoot) { backfillCoversButton.disabled = loading || count === 0; backfillCoversButton.textContent = loading ? `正在补充${platformLabel()} ${count} 条封面…` - : `一键补充${platformLabel()}缺失封面${count ? `(${count})` : ""}`; + : `补齐${platformLabel()}缺失封面${count ? `(${count})` : ""}`; } function beijingDatetimeValue(timestamp) { @@ -1128,6 +1128,8 @@ if (publishCenterRoot) { panel.hidden = !active; panel.classList.toggle("active", active); }); + const batchAiButton = document.querySelector("[data-batch-ai]"); + if (batchAiButton) batchAiButton.hidden = tab !== "content"; if (tab === "history") void refreshHistory({ calendar: true, records: true }); } @@ -1400,24 +1402,6 @@ if (publishCenterRoot) { } } - async function upgradePendingDouyinMetadata() { - try { - const data = await window.apiFetch("/api/publish/jobs/metadata/upgrade-pending-douyin", { method: "POST" }); - (data.jobs || []).forEach((job) => { - const row = document.querySelector( - `[data-publish-row][data-section="content"][data-job-id="${CSS.escape(job.id || "")}"]`, - ); - applyGeneratedMetadataToForm(row, job); - updateRowFromJob(job); - }); - if (Number(data.upgraded_count || 0) > 0 || Number(data.failed_count || 0) > 0) { - showMessage(data.message || "抖音旧草稿文案升级完成。", data.failed_count ? "error" : "success"); - } - } catch (error) { - showMessage(`旧草稿文案升级已中止:${error.message}`, "error"); - } - } - async function refreshAccounts() { try { const data = await window.apiFetch("/api/publish/accounts"); @@ -1866,8 +1850,8 @@ if (publishCenterRoot) { const data = await window.apiFetch(`/api/publish/jobs/${jobId}/metadata?use_ai=true`, { method: "POST" }); applyGeneratedMetadataToForm(row, data.job); updateRowFromJob(data.job); - showMessage("AI 文案已重写并同步到最终发送字段。", "success"); - } catch (error) { showMessage(`AI 重写失败:${error.message}`, "error"); } + showMessage("本条 AI 文案已重写并保存到最终发送字段。", "success"); + } catch (error) { showMessage(`本条 AI 重写失败:${error.message}`, "error"); } finally { metadataButton.disabled = false; } return; } @@ -2052,7 +2036,7 @@ if (publishCenterRoot) { } } showMessage( - `批量 AI 重写完成:成功 ${succeeded} 条,失败 ${failed} 条;失败项已保留原文。`, + `已选文案 AI 重写完成:成功 ${succeeded} 条,失败 ${failed} 条;失败项已保留原文。`, failed ? "error" : "success", ); }); @@ -2200,8 +2184,8 @@ if (publishCenterRoot) { const button = event.currentTarget; button.disabled = true; try { const data = await window.apiFetch(`/api/publish/queue/refresh?use_ai=false&platform=${encodeURIComponent(activePlatform)}`, { method: "POST" }); - showMessage(data.message || "缺失任务已补充,请稍后查看内容准备区。", "success"); - } catch (error) { showMessage(`补充任务失败:${error.message}`, "error"); } + showMessage(data.message || "遗漏切片已同步,请在内容准备区核对。", "success"); + } catch (error) { showMessage(`同步遗漏切片失败:${error.message}`, "error"); } finally { button.disabled = false; } }); @@ -2216,7 +2200,6 @@ if (publishCenterRoot) { if (scheduleForm?.elements.start_at_local) scheduleForm.elements.start_at_local.value = beijingDatetimeValue(Date.now() + 10 * 60 * 1000); document.querySelectorAll('[data-publish-row][data-section="schedule"], [data-publish-row][data-section="history"]').forEach((row) => applyRowReadiness(row)); updateSelectionUi(); applyHistoryFilter(); refreshScheduleViews(); updateBackfillCoversButton(); - void upgradePendingDouyinMetadata(); if (focus?.dataset.taskId) { const group = document.querySelector( `[data-publish-task-group][data-task-id="${CSS.escape(focus.dataset.taskId)}"]`, diff --git a/app/templates/publish.html b/app/templates/publish.html index 28a9107..ba870cf 100644 --- a/app/templates/publish.html +++ b/app/templates/publish.html @@ -33,6 +33,13 @@ +
+
+ 发布文案 + AI 重写会立即生成并保存本条标题、话题和简介 +
+ +
- @@ -145,7 +151,7 @@ >

Publish Center

发送中心

先准备内容,再按北京时间排期,最后在执行记录中确认平台结果。

-
+
@@ -183,17 +189,30 @@ {% set initial_platform = "douyin" %} {% set initial_missing_cover_count = missing_cover_counts.get(initial_platform, 0) %}
-

内容准备

这里不展示历史错误;内容、平台和账号完整后再加入排期。

+

内容准备

逐条编辑文案,或勾选多条后批量重写;内容、平台和账号完整后再加入排期。

+
+
{% for group in publish_task_groups %}
@@ -318,7 +337,7 @@

全部执行记录

仅限 抖音 - +
diff --git a/docs/TASK_FLOW.md b/docs/TASK_FLOW.md index 2e92901..26b9af4 100644 --- a/docs/TASK_FLOW.md +++ b/docs/TASK_FLOW.md @@ -236,7 +236,7 @@ output_clip 生成成功 └─ opencli_publish → 显式兼容开关 → PUBLISHED / FAILED / NEED_REVIEW ``` -`platform` 只能是 `douyin` / `bilibili`;`publish_mode` 只能表示执行方式,禁止互相混用。发送中心的“补充缺失任务”只补缺,不覆盖已有任务的执行方式。 +`platform` 只能是 `douyin` / `bilibili`;`publish_mode` 只能表示执行方式,禁止互相混用。发送中心内容准备区的“同步遗漏切片”固定使用 `use_ai=false`,只补建尚未进入发送中心的草稿与默认封面,不覆盖已有任务的执行方式或文案。AI 文案仅由内容卡“AI 重写本条文案”或明确勾选后的“AI 重写已选文案”触发;页面加载、排期计划和执行记录不会调用 AI 重写。 ### 发送任务状态(publish_jobs.status) diff --git a/docs/UI_REFERENCE.md b/docs/UI_REFERENCE.md index e6eef52..9cc2ebf 100644 --- a/docs/UI_REFERENCE.md +++ b/docs/UI_REFERENCE.md @@ -1,5 +1,14 @@ # UI 参考说明 +## 2026-08-26 更新:发送中心 AI 文案操作分层 + +- 页面顶栏只保留账号管理;原“补充缺失任务”改名为“同步遗漏切片”,与“补齐抖音缺失封面”共同放入内容准备维护区。 +- 维护区使用独立浅色条明确说明:同步动作只补建遗漏草稿和默认封面,不调用 AI、不修改已有文案。 +- 每条内容卡的 AI 按钮紧贴发布文案字段,命名为“AI 重写本条文案”;勾选内容后的固定操作栏使用“AI 重写已选文案”,范围一眼可见。 +- 批量 AI 仅在内容准备标签页显示;排期计划只处理时间,执行记录只处理结果和人工复核。 +- 页面打开和刷新不再静默执行旧草稿全局 AI 升级,AI 文案必须由用户明确点击触发。后端兼容接口保留,不改变历史数据或发布状态机。 +- 窄屏下维护区与单条文案工具栏改为纵向排列,按钮使用整行宽度。 + ## 2026-08-26 更新:片段审核累计统计 - 片段审核工作台顶部只保留三项结果指标:“累计审核任务、已通过视频、已完成任务”,移除待 AI、待检查、可生成切片和异常任务等过程型统计。 @@ -80,8 +89,8 @@ - 发送中心全局平台卡只保留抖音;`platform=bilibili` 深链参数会被忽略。B站历史记录、API 和 Publisher 继续保留在后端,但当前前台不展示 B站任务或发送入口。 - 内容卡账号状态直接复用统一发送预检:唯一正常抖音账号自动选中并显示“内容完整”,多账号显示“缺少:发布账号”,登录失效显示“账号需登录”。 - 每个原始处理任务组头增加“全选本任务 / 取消全选”复选框;只作用于本组当前可操作的抖音视频,支持部分选择状态,任务组折叠时仍可使用。 -- 标题输入限制 30 字并显示实时计数;标签提示 4~6 个、每个 2~3 字;简介限制 15~35 字并显示实时计数。单条和批量按钮统一命名为“AI 重写文案”,成功后立即刷新三个输入框。 -- 页面首次打开会异步调用幂等旧草稿升级;成功项原地更新,失败项提示保留原文。页面仍保持 Apple 风格浅色卡片、玻璃感和响应式布局,不引入前端框架。 +- 标题输入限制 30 字并显示实时计数;标签提示 4~6 个、每个 2~3 字;简介限制 15~35 字并显示实时计数。单条与批量入口分别命名为“AI 重写本条文案”和“AI 重写已选文案”,成功后立即刷新三个输入框。 +- 旧草稿升级接口继续保留后台兼容,但页面不再自动触发;AI 文案只通过单条或明确勾选后的批量入口执行。页面仍保持 Apple 风格浅色卡片、玻璃感和响应式布局,不引入前端框架。 ## v2.1 界面总览 diff --git a/docs/agent_tasks/2026-08-26-publish-ai-action-placement.md b/docs/agent_tasks/2026-08-26-publish-ai-action-placement.md new file mode 100644 index 0000000..e5a2538 --- /dev/null +++ b/docs/agent_tasks/2026-08-26-publish-ai-action-placement.md @@ -0,0 +1,64 @@ +# 发送中心 AI 文案操作分层 + +## 背景 + +发送中心顶部的“补充缺失任务”和勾选内容后的“批量 AI 重写文案”位置接近、说明不足,容易被理解成重复操作。实际前者通过 `use_ai=false` 补建遗漏的发布草稿并生成默认封面,后者会逐条调用 AI 并覆盖已选草稿的标题、话题和简介。页面加载时还会静默触发一次旧草稿全局 AI 升级,不符合 AI 必须由用户明确选择后执行的交互边界。 + +## 目标 + +- 将补建草稿与 AI 文案操作按“维护动作 / 内容动作”清晰分层。 +- AI 重写只保留在内容准备中的单条和显式勾选批量入口。 +- 排期计划和执行记录不展示 AI 重写。 +- 页面打开不再自动调用旧草稿全局 AI 升级。 + +## 允许修改范围 + +- `app/templates/publish.html` +- `app/static/css/styles.css` +- `app/static/js/publish-center.js` +- `tests/test_publish_center_cleanup.py` +- `tests/test_publish_center_browser.py`(仅相关断言需要时) +- `DEVELOPMENT_LOG.md` +- `NEXT_STEPS.md` +- `docs/UI_REFERENCE.md` +- `docs/TASK_FLOW.md` + +## 禁止修改范围 + +- 发布任务、调度器、Publisher 和账号的后端状态机。 +- AI Prompt、Provider、模型、认证或计费配置。 +- SQLite Schema、迁移和活动数据。 +- 真实 AI、封面生成、排期或平台发布操作。 + +## 已确定实现要求 + +- 页面顶栏只保留“账号管理”;“补充缺失任务”移入内容准备的维护区,改名为“同步遗漏切片”。 +- 维护区明确说明:只补建遗漏草稿和默认封面,不调用 AI、不修改已有文案。 +- 单条按钮放在发布文案字段上方,命名为“AI 重写本条文案”,明确会立即生成并保存本条标题、话题和简介。 +- 勾选后的按钮命名为“AI 重写已选文案”,仅在内容准备标签页显示;排期与执行记录不显示 AI 重写。 +- 移除页面加载时对 `/api/publish/jobs/metadata/upgrade-pending-douyin` 的自动调用;保留后端兼容接口,不删除历史能力。 +- 补建遗漏草稿继续固定使用 `use_ai=false`,不改变后端语义。 + +## 验收标准 + +- 顶部“账号管理”旁不再出现含糊的“补充缺失任务”。 +- 内容准备区能看到“同步遗漏切片”和清晰的无 AI 说明。 +- 每条内容卡只显示“AI 重写本条文案”;勾选后显示“AI 重写已选文案”。 +- 切换到排期计划或执行记录后,批量 AI 按钮隐藏。 +- 打开或刷新 `/publish` 不请求旧草稿全局 AI 升级接口。 +- 现有单条、批量、补建、封面和排期后端接口保持不变。 +- 桌面与 390px 窄屏布局可读,无横向溢出。 + +## 测试命令 + +- `.venv\Scripts\python.exe -m pytest tests/test_publish_center_cleanup.py tests/test_publish_center_browser.py tests/test_publish_copy_rules.py tests/test_publish_scheduler.py tests/test_publish_task_grouping.py tests/test_publish_api_flow.py -q` +- `.venv\Scripts\python.exe -m pytest -q` +- `.venv\Scripts\python.exe -m compileall -q app tests` +- `node --check app/static/js/publish-center.js` +- `.venv\Scripts\python.exe -m ruff check tests/test_publish_center_cleanup.py tests/test_publish_center_browser.py` +- `git diff --check` +- 浏览器验证 `/publish` 的桌面与 390px 窄屏按钮层级,并通过前端请求路径断言确认刷新时没有自动 AI 升级调用。 + +## 返回格式 + +报告两个动作的最终区别、AI 按钮放置、修改文件、测试与浏览器证据、分支、提交、推送和 PR;不得把本地页面验证描述为真实 AI 或平台发布验证。 diff --git a/tests/test_publish_center_browser.py b/tests/test_publish_center_browser.py index d0f40b8..38e8b20 100644 --- a/tests/test_publish_center_browser.py +++ b/tests/test_publish_center_browser.py @@ -253,8 +253,9 @@ def fulfill_metadata(route): group_select.check() assert first_content.locator("[data-publish-select]").is_checked() assert second_content.locator("[data-publish-select]").is_checked() + assert page.locator("[data-batch-ai]").inner_text() == "AI 重写已选文案" page.locator("[data-batch-ai]").click() - page.locator("#send-center-message").filter(has_text="批量 AI 重写完成:成功 2 条").wait_for() + page.locator("#send-center-message").filter(has_text="已选文案 AI 重写完成:成功 2 条").wait_for() assert first_content.locator('[name="description"]').input_value() == "陈汉典刚说自己像潘玮柏,小S立刻给出另一答案" assert second_content.locator('[name="tags"]').input_value() == "综艺,高光,小S,反转" first_content.locator("[data-publish-select]").uncheck() @@ -272,6 +273,7 @@ def fulfill_metadata(route): group_select.uncheck() page.locator('[data-center-tab="schedule"]').click() + assert page.locator("[data-batch-ai]").is_hidden() assert page.locator('[data-schedule-calendar] .publish-calendar-day').count() == 42 assert "抖音" in page.locator("[data-calendar-title]").inner_text() assert page.locator( diff --git a/tests/test_publish_center_cleanup.py b/tests/test_publish_center_cleanup.py index c69eb24..a0c20b9 100644 --- a/tests/test_publish_center_cleanup.py +++ b/tests/test_publish_center_cleanup.py @@ -14,9 +14,33 @@ def test_selection_bar_has_no_legacy_batch_send_but_keeps_scheduling() -> None: assert "data-open-schedule-drawer" in selection_bar assert "data-apply-batch-target" in selection_bar assert "data-batch-ai" in selection_bar + assert "AI 重写已选文案" in selection_bar assert "data-publish-now" in template +def test_publish_ai_and_maintenance_actions_are_scoped_to_content_preparation() -> None: + template = (PROJECT_ROOT / "app/templates/publish.html").read_text(encoding="utf-8") + publish_script = (PROJECT_ROOT / "app/static/js/publish-center.js").read_text(encoding="utf-8") + page_heading = template.split('
', 1)[1].split( + "
", 1 + )[0] + content_panel = template.split('data-center-panel="content"', 1)[1].split( + 'data-center-panel="schedule"', 1 + )[0] + content_row_macro = template.split("{% macro content_row(job) %}", 1)[1].split( + "{% endmacro %}", 1 + )[0] + + assert "data-supplement-publish-jobs" not in page_heading + assert "data-supplement-publish-jobs" in content_panel + assert "同步遗漏切片" in content_panel + assert "不调用 AI,也不修改已有文案" in content_panel + assert "AI 重写本条文案" in content_row_macro + assert "use_ai=false" in publish_script + assert "/api/publish/jobs/metadata/upgrade-pending-douyin" not in publish_script + assert 'batchAiButton.hidden = tab !== "content"' in publish_script + + def test_legacy_publish_frontend_handlers_are_removed() -> None: global_script = (PROJECT_ROOT / "app/static/js/app.js").read_text(encoding="utf-8") publish_script = (PROJECT_ROOT / "app/static/js/publish-center.js").read_text(encoding="utf-8")