From 32565a78818be446dc0d815c2d9bad83b503ad89 Mon Sep 17 00:00:00 2001 From: maogeigei Date: Fri, 4 Sep 2026 12:08:47 +0800 Subject: [PATCH] =?UTF-8?q?R55-R58:=20AI=E8=84=9A=E6=9C=AC=E5=BC=B9?= =?UTF-8?q?=E7=AA=97=E7=89=88=E6=9C=AC=E6=9D=A1(=E6=96=B0=E5=9C=A8?= =?UTF-8?q?=E5=8F=B3=E6=97=A7=E5=9C=A8=E5=B7=A6)+=E6=95=99=E7=A8=8B?= =?UTF-8?q?=E5=AE=8C=E6=95=B4=E9=93=BE=E7=BB=B4=E5=BA=A6=E4=B8=8D=E9=80=82?= =?UTF-8?q?=E7=94=A8=E5=8D=B3=E9=9A=90=E8=97=8F+=E7=83=AD=E7=82=B9?= =?UTF-8?q?=E8=B4=A6=E5=8F=B7=E6=97=A5=E6=A6=9C+=E8=84=9A=E6=9C=AC?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=8C=89=E9=92=AE(=E7=BA=A7=E8=81=94?= =?UTF-8?q?=E8=AF=8A=E6=96=AD=E5=88=86=E9=95=9C,=20=E5=90=ABhelp-guide.js?= =?UTF-8?q?=E8=A1=A5=E7=BA=B3=E5=85=A5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .workbuddy/memory/2026-09-04.md | 43 ++ .../V1.0/mcn-work-shop/dsh-data.js | 37 +- .../V1.0/mcn-work-shop/public/app.js | 54 ++- .../V1.0/mcn-work-shop/public/data-pages.js | 203 +++++--- .../V1.0/mcn-work-shop/public/help-guide.js | 438 ++++++++++++++++++ .../V1.0/mcn-work-shop/public/index.html | 7 +- .../V1.0/mcn-work-shop/public/style.css | 58 +++ .../V1.0/mcn-work-shop/server.js | 14 + 8 files changed, 747 insertions(+), 107 deletions(-) create mode 100644 project/短视频脚本创作/V1.0/mcn-work-shop/public/help-guide.js diff --git a/.workbuddy/memory/2026-09-04.md b/.workbuddy/memory/2026-09-04.md index 6d902d9..d97c3d2 100644 --- a/.workbuddy/memory/2026-09-04.md +++ b/.workbuddy/memory/2026-09-04.md @@ -25,3 +25,46 @@ - **改动 6 文件 8 处全量替换**(grep 零残留验证):`index.html` ``、`app.js` 面包屑 crumb-home ×2 + 头注释、`data-pages.js`/`style.css`/`dsh-data.js` 头注释、`server.js` 头注释 + 启动日志;首页无独立大字标题,品牌名只在此两处 UI 呈现 - **缓存版本统一 bump** `?v=20260904a`(style/app/data-pages 三文件引用);brand 区曾误加 brand-name 文字会与 crumb-home 重复显示→已撤销 - **服务重启生效**:杀 8900 旧进程(34076)→ 重启(24836),curl 验证 `<title>短视频工作台` + crumb-home 文本正确 + +## AI脚本弹窗版本条重构:新在右 旧在左(R55) +- 用户反馈:「AI脚本 弹窗内容下面的版本切换 不好理解,默认选中的是 最新版 还是 最老的版本」→ 答复=默认最新(rewrite_log ORDER BY id DESC,索引0=最新,verIdx=0);AskUserQuestion 确认「方向词+最新标记」;追加要求「新的放在右边 旧的放在左边」 +- **实施(data-pages.js openScriptModal)**:①`rewrites = rewrites.slice().reverse()` 展示层 旧→新(副本翻转,最左=最初版、最右=最新版);②verIdx 默认=length-1(最新/最右),指定 rewriteId 时 findIndex 定位(找不到回最新);③storyboards 同法 reverse(末位=最新分镜);④sbIdx 语义改 -1=未选 → sideContent 自动钳制到该 AI 版本最新分镜(length-1),切版本按钮里 sbIdx=0 改 -1(vprev/vnext 绑定代码本身零改动——数组翻转后方向自动正确);⑤verCtrl 模板:`{label} {idx+1}/{len} · {fmtTime(created_time)} [最新徽标]` + 按钮方向词「← 较旧 / 较新 →」(idx+1=旧到新第几版,末位标最新) +- **样式**:style.css 新增 `.rw-ver-new`(primary 实底圆角徽标),count 区时间用 .dim +- **版本号**:style.css/data-pages.js bump `?v=20260904b→c`;服务重启(后台 task);node --check 语法通过 +- **待实测**:浏览器授权后验证——默认打开=最新在最右带徽标、←较旧左移、较旧侧 disabled 边界、分镜列同样式同方向 + +## R55 版本条重构浏览器实测通过(11:40,9223 实例) +- **环境修复**(铺垫):用户 Chrome 默认 profile 9222 调试端点不响应(DevToolsActivePort 残留但 socket 无监听 + 启动独立 daemon 失败)→ 改用项目既有惯例:启动 9223 专用调试 Chrome `chrome.exe --remote-debugging-port=9223 --user-data-dir=C:/Users/maidou/.workbuddy/tmp/bh-chrome-9223`(固定 profile 已 09-02 授权过,直接返回 Browser JSON 零弹窗),`BU_CDP_URL=http://127.0.0.1:9223` 定向连接 → harness 不再反复弹授权(弹窗根因=连无授权实例,现连已授权 profile) +- **AI 脚本版(video 190 = 3 版)实测**:初始 3/3 · 08-24 17:45 最新 + 较新→disabled;← 较旧→2/3 (17:29) 双向可用;再← →1/3 (08-22 16:44) ← disabled;较新→ 回到 2/3 ✓ +- **分镜版(video 189 = 1 AI + 2 分镜)实测**:初始 分镜 2/2 · 08-22 14:39 最新 + 较新→disabled;← 较旧→分镜 1/2 (08-21 23:06) ← disabled ✓ — 切回 2/2 正常 +- **最新徽标 CSS**:bg=primary rgb(47,111,237) 蓝实底,color=#fff,fontWeight=600,fontSize=12px,borderRadius=9px,pad 0 7px,marginLeft 6px +- **方向词+时间+徽标+边界 disabled+切版本分镜自动定位最新** 全部通过 +- **遗留**:9223 调试 Chrome 实例已启动常驻(按项目红线「不主动关闭」保留,后续 task 可复用) + +## R56 教程完整链维度「不适用即隐藏」(11:50,实测通过) +- 用户诉求:脚本诊断报告中,当前脚本不需要「教程完整链」分析时就不展示该维度(剧情类脚本不评教程维度,现显示灰色 — 占位行) +- **数据事实**:script_review.dims_json 中模型对不适用脚本把「教程完整链」置 **null**(12 条复盘全部 null;其余维度有值) +- **落码(data-pages.js)**:新增 `diagDimHas(dims,k)`(值是否为有限数字)+ `diagVisibleDims(dims)` = 教程完整链有值→完整 11 维列表 / null 或缺失→过滤掉该维度;其余维度无值仍显示 — 灰条(不超范围改动) +- **3 处渲染统一替换**:①弹窗右侧诊断面板 diagHtml scoreBar;②完整报告弹窗 reportFullReview scoreBar(compare 双列 aiD/srcD 各自独立判断);③脚本诊断页 openReviewDetail scoreBar(删本地 DIMS_ALL 副本改引用顶层 helper) +- **实测(9223)**:video 190(剧情类复盘)弹窗诊断面板与「查看完整复盘报告」均为 10 行维度(创意看点…其他),无教程完整链;反例(教程类有值 88)逻辑显式返回 11 项 +- **版本号**:data-pages.js bump ?v=20260904c→d + +## R57 热点数据页新增「账号日榜」(12:10,实测通过) +- 用户诉求:热点数据榜单加「账号日榜」,放「账号周榜」右边;获取用**不传赛道**方式(全品类 1 次调用),只显示需要的赛道数据 +- **事实**:douyin-top-account fetch_rank.py --period day 不传 category → API type=全部 全品类 TOP50 单次调用(to_normalized_json 条目带真实赛道 category + comprehensiveScore 等与周榜同构) +- **落码**: + - 后端 dsh-data.js:RANK_SUB/RANK_FILE_RE 加 `day: '账号日榜' / ^抖音账号日榜_(\d{4}-\d{2}-\d{2})\.json$`;DEF_KEY week||day → comprehensiveScore(账号榜维度,防 day 误落作品榜 key);白名单过滤分支 board!=='week' 自动覆盖 day(RANK_KEEP 5 赛道+生活记录并集) + - ranking 页 data-pages.js:RANK_TABS 插 ['day','账号日榜','每日更新,回溯 7 天'](周榜右);st.day;COLS 抽 acctCol(empty) 工厂 week/day 共用(day empty 提示含「更新榜单可抓取,不传赛道全品类」);表格 rank-video 分支加 day(账号榜非窄列);更新按钮/确认弹窗文案四榜 8 次 API;SKILL_HINT_RANKING 加日榜抓取指令(账号日榜/抖音账号日榜_{日期}.json) + - 首页预览 app.js:tab 行加 day(title 每日更新回溯7天)、_PREVIEW_COLS 抽 prevAcctCol week/day 共用、空态榜名映射加 day、热点数据卡 desc 改「账号日榜/周榜 · 视频热榜 · 点赞榜」 +- **实测(9223 + fake 文件验证后删除)**:后端 board=day → dates/categories(5 赛道 RANK_KEEP 序)/10 条过滤非白名单剩 8;ranking 页 tab 顺序=周榜|日榜|热榜|点赞榜、10 列表头同 week、赛道下拉 5 项、筛小剧场 2 行;首页预览 day 正常。fake 文件已删(真实抓取等「更新榜单」任务,1 次全品类 API) +- **版本号**:app.js/data-pages.js bump ?v=20260904e;服务重启(dsh-data.js 模块变更) + +## R58 AI脚本弹窗删除按钮(15:20,全路径实测通过) +- 用户诉求:AI脚本弹窗 AI脚本内容区域左下角增加浅红删除按钮,确认后删除当前脚本(含相关诊断和分镜提示词数据) +- **后端(dsh-data.js)**:`deleteScript(rewriteId)` 级联清理——rewrite_log 删除 → script_review(rewrite_id)+ storyboard_log(rewrite_id);该视频无剩余脚本 → 再清 source 复盘与游离分镜;独立 DatabaseSync 写连接(与只读 getDb 隔离),finally w.close();**必须加 module.exports 导出**(漏=server.js 报 dsh.deleteScript is not a function,R58 踩过一次) +- **server.js**:`/api/dsh/script-delete` POST({id})路由 +- **前端(data-pages.js)**:`.btn-del` 浅红按钮(style.css:#fdf0ef/#f0c9c6/#d9534f + hover 深红)+ `fireDel` 确认弹窗(级联提示文案);多版本 → splice + renderModal() 就地重建(剩余版本、定位相邻版);单版本 isLast → router() 整页重建;verCtrlInner 拆分复用 aiSecHtml 单版本 foot +- **★openCustomScriptView 移除(关键改造)**:自定义选题「查看」(data-cview)原绑 openCustomScriptView(09-02 简单只读弹窗:无删除/无诊断/无版本条);openScriptModal rewriteId 分支本已支持自定义单条(custom-script API + 回退全量表定位),改绑 `openScriptModal({rewriteId})` 后统一弹窗(删除按钮+诊断面板齐备),死代码 openCustomScriptView 整体删除(grep 确认仅绑定处 1 引用) +- **实测(9223,fake 全清)**:①多版本(video 190 4 版)删第 4/4 → 弹窗重建 3/3 内容切 rw25 ✅;②单版本视频脚本 isLast → router 刷新 ✅;③自定义选题 id38(video_id 空)查看弹窗含删除按钮 → 确认删除 → 弹窗关+行消失,DB 复查 rewrite/review/storyboard 三级全 0 ✅ +- **版本号**:data-pages.js bump ?v=20260904e→f(静态 js 实时读盘未重启) +- **遗留**:R55-R58 改动未 commit(待 git 提交);帮助文档一行两卡 + 模拟操作指引点击无效果仍待修 diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/dsh-data.js b/project/短视频脚本创作/V1.0/mcn-work-shop/dsh-data.js index e70c0c3..2d68c3b 100644 --- a/project/短视频脚本创作/V1.0/mcn-work-shop/dsh-data.js +++ b/project/短视频脚本创作/V1.0/mcn-work-shop/dsh-data.js @@ -104,6 +104,32 @@ function deleteVideosDeep(w, vids) { } } +// 删除单条 AI 脚本(R58 弹窗「删除」):连坐该脚本的诊断复盘(script_review ai/compare)与分镜提示词(storyboard_log)。 +// 若删除后其视频下已无剩余脚本 → 视频级 source 复盘与该视频游离分镜(rewrite_id 空)一并清理,避免孤儿数据。 +function deleteScript(rewriteId) { + const id = Number(rewriteId); + if (!(id > 0)) throw new Error('脚本 ID 无效'); + if (!fs.existsSync(DSH_DB)) throw new Error('数据库不可用'); + const w = new DatabaseSync(DSH_DB); + try { + const row = w.prepare(`SELECT id, video_id, aweme_id FROM rewrite_log WHERE id=?`).get(id); + if (!row) return { deleted: 0 }; + w.prepare(`DELETE FROM script_review WHERE rewrite_id=?`).run(id); /* ai/compare 诊断 */ + w.prepare(`DELETE FROM storyboard_log WHERE rewrite_id=?`).run(id); /* 该脚本分镜 */ + if (row.video_id) { + const remain = w.prepare(`SELECT COUNT(*) c FROM rewrite_log WHERE video_id=? AND id<>?`).get(row.video_id, id).c; + if (remain === 0) { + w.prepare(`DELETE FROM script_review WHERE type='source' AND video_id=?`).run(row.video_id); + w.prepare(`DELETE FROM storyboard_log WHERE video_id=? AND (rewrite_id IS NULL OR rewrite_id=0)`).run(row.video_id); + } + } + const r = w.prepare(`DELETE FROM rewrite_log WHERE id=?`).run(id); + return { deleted: Number(r.changes) }; + } finally { + w.close(); + } +} + /* ================= 账号 ================= */ function listTracks() { @@ -341,16 +367,17 @@ function listRecentScripts(limit = 4) { // 根目录旧平铺文件仍兼容读取(任务旧产物)。 // 全品类单文件(hot/likes)无赛道段:category 顶层标记「全部」,实际赛道在 list[].category, // 赛道下拉 = 各期文件条目 category 并集,赛道筛选按条目 category 匹配。 -const RANK_SUB = { week: '账号周榜', hot: '视频热榜', likes: '点赞榜' }; +const RANK_SUB = { week: '账号周榜', day: '账号日榜', hot: '视频热榜', likes: '点赞榜' }; const RANK_FILE_RE = { week: /^抖音周榜_(.+)_(\d{4}-\d{2}-\d{2})\.json$/, + day: /^抖音账号日榜_(\d{4}-\d{2}-\d{2})\.json$/, hot: /^抖音周榜_视频热榜_(\d{4}-\d{2}-\d{2})\.json$/, likes: /^抖音周榜_点赞榜_(\d{4}-\d{2}-\d{2})\.json$/, }; // 工作台所需赛道白名单(09-03 定:与账号周榜 douyin-top-account 保留 5 赛道同口径) // 口径差异:账号榜 type=「生活vlog」;作品榜条目 category=「生活记录」——并集同时放行。 -// 生效范围:hot/likes(全品类文件)赛道下拉与列表(含「全部」)只保留白名单条目;week 文件名赛道天然在白名单内。 +// 生效范围:day/hot/likes(全品类文件)赛道下拉与列表(含「全部」)只保留白名单条目;week 文件名赛道天然在白名单内。 const RANK_KEEP = ['生活记录', '生活vlog', '小剧场', '亲子', '美食', '旅行']; const RANK_KEEP_SET = new Set(RANK_KEEP); @@ -464,8 +491,8 @@ function getRanking({ board = 'week', date = '', category = '', sortBy = '', ord items = items.filter((it) => String(it.category || '').trim() === category); } } - // 默认排序键随榜:week=综合得分 / hot=点赞 / likes=新增点赞(同分按排名升序) - const DEF_KEY = board === 'week' ? 'comprehensiveScore' : board === 'hot' ? 'likeCount' : 'add_digg_count'; + // 默认排序键随榜:week/day(账号榜)=综合得分 / hot=点赞 / likes=新增点赞(同分按排名升序) + const DEF_KEY = (board === 'week' || board === 'day') ? 'comprehensiveScore' : board === 'hot' ? 'likeCount' : 'add_digg_count'; if (!order && (!sortBy || sortBy === DEF_KEY)) { items.sort((a, b) => { const va = num(a[DEF_KEY]), vb = num(b[DEF_KEY]); @@ -565,7 +592,7 @@ function getRewriteStats(account = '') { } module.exports = { - dbAvailable, setFavorite, batchDelete, listTracks, listAccounts, listAccountVideos, getPersona, getAccountAnalysis, + dbAvailable, setFavorite, batchDelete, deleteScript, listTracks, listAccounts, listAccountVideos, getPersona, getAccountAnalysis, listVideos, getVideoDetail, getVideoScript, listRewrites, listStoryboards, listRecentScripts, getReviewDetail, getReviewFull, getStats, rankingMeta, getRanking, rankingFreshness, DSH_DB, RANKING_DIR, listCustomScripts, getCustomScript, saveScript, getRewriteStats, diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/public/app.js b/project/短视频脚本创作/V1.0/mcn-work-shop/public/app.js index 13ffd66..11d13a5 100644 --- a/project/短视频脚本创作/V1.0/mcn-work-shop/public/app.js +++ b/project/短视频脚本创作/V1.0/mcn-work-shop/public/app.js @@ -302,7 +302,7 @@ const FUNC_GROUPS = [ /* ================= 首页 ================= */ function renderHome() { const cards = [ - { icon: 'trending-up', title: '热点数据', desc: '账号周榜 · 视频热榜 · 点赞榜', hash: '#/ranking' }, + { icon: 'trending-up', title: '热点数据', desc: '账号日榜/周榜 · 视频热榜 · 点赞榜', hash: '#/ranking' }, { icon: 'users', title: '账号列表', desc: '关注的账号 + 视频列表', hash: '#/accounts' }, { icon: 'pen', title: 'AI写脚本', desc: 'AI脚本+分镜提示词', hash: '#/rewrites' }, { icon: 'flask', title: '脚本诊断', desc: '检查清单+复盘报告', hash: '#/reviews' }, @@ -327,10 +327,11 @@ function renderHome() {
+
- +
@@ -474,20 +475,22 @@ function renderRankTools() { if (nextBtn) { nextBtn.disabled = di < 0 || di >= _rankPreview.dates.length - 1; nextBtn.onclick = () => { if (di >= 0 && di < _rankPreview.dates.length - 1) { _rankPreview.date = _rankPreview.dates[di + 1]; renderRankTools(); loadRankingPreview(); } }; } } // 首页各榜列模板:head → 表头行;row(r) → 数据行。wk-num 数值(万显示);标题/作者吃剩余宽 +const prevAcctCol = () => ({ + gridCls: '', + head: `排名赛道账号综合得分新增粉丝新增点赞新增评论新增分享功能`, + row: (r) => `${r.rank ?? '-'} + ${esc(r.category || '-')} + ${r.inDb && r.dbId ? `${esc(r.accountName)}` : (r.profileUrl ? `${esc(r.accountName)}` : esc(r.accountName))} + ${fmt(r.comprehensiveScore)} + ${esc(r.newFans ?? '-')} + ${esc(r.newLikes ?? '-')} + ${esc(r.newComments ?? '-')} + ${esc(r.newShares ?? '-')} + ${r.inDb && r.dbId ? `查看` : ``}`, +}); const _PREVIEW_COLS = { - week: { - gridCls: '', - head: `排名赛道账号综合得分新增粉丝新增点赞新增评论新增分享功能`, - row: (r) => `${r.rank ?? '-'} - ${esc(r.category || '-')} - ${r.inDb && r.dbId ? `${esc(r.accountName)}` : (r.profileUrl ? `${esc(r.accountName)}` : esc(r.accountName))} - ${fmt(r.comprehensiveScore)} - ${esc(r.newFans ?? '-')} - ${esc(r.newLikes ?? '-')} - ${esc(r.newComments ?? '-')} - ${esc(r.newShares ?? '-')} - ${r.inDb && r.dbId ? `查看` : ``}`, - }, + week: prevAcctCol(), + day: prevAcctCol(), hot: { // likesRank 周窗口作品榜 gridCls: ' rk-video', @@ -533,7 +536,7 @@ async function loadRankingPreview() { } if (!_rankPreview.dates.length) { listEl.hidden = true; - if (ph) { ph.hidden = false; ph.textContent = '「' + ({ week: '账号周榜', hot: '视频热榜', likes: '点赞榜' }[board] || '') + '」暂无本地数据文件,先运行 mcn-data-insight 抓取技能落盘'; } + if (ph) { ph.hidden = false; ph.textContent = '「' + ({ week: '账号周榜', day: '账号日榜', hot: '视频热榜', likes: '点赞榜' }[board] || '') + '」暂无本地数据文件,先运行 mcn-data-insight 抓取技能落盘'; } return; } if (ph) ph.hidden = true; @@ -758,12 +761,19 @@ async function router() { } if (name === 'help') { updateCrumbs('help', '功能帮助'); - view.innerHTML = renderHelp(); - bindBack(view); // 返回 = 上一页(无历史则回首页) - view.querySelectorAll('.qa-row.copyable').forEach((row) => row.addEventListener('click', async () => { - const text = (row.dataset.q || '').replace(/^"|"$/g, ''); - if (text) showToast((await copyText(text)) ? '✅ 已复制:' + text : '❌ 复制失败'); - })); + // 09-04:板块化帮助 + 模拟操作引导(help-guide.js);文件缺失时回退旧版问法帮助 + if (window.HelpGuide && typeof window.HelpGuide.renderHelp === 'function') { + view.innerHTML = window.HelpGuide.renderHelp(); + bindBack(view); // 返回 = 上一页(无历史则回首页) + window.HelpGuide.bindHelp(view); + } else { + view.innerHTML = renderHelp(); + bindBack(view); // 返回 = 上一页(无历史则回首页) + view.querySelectorAll('.qa-row.copyable').forEach((row) => row.addEventListener('click', async () => { + const text = (row.dataset.q || '').replace(/^"|"$/g, ''); + if (text) showToast((await copyText(text)) ? '✅ 已复制:' + text : '❌ 复制失败'); + })); + } return; } if (name === 'files') { diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/public/data-pages.js b/project/短视频脚本创作/V1.0/mcn-work-shop/public/data-pages.js index 4db1d1a..8e05f35 100644 --- a/project/短视频脚本创作/V1.0/mcn-work-shop/public/data-pages.js +++ b/project/短视频脚本创作/V1.0/mcn-work-shop/public/data-pages.js @@ -21,10 +21,14 @@ const SKILL_HINT_VIDEO_PARSE = '请调用 Skill 工具加载「mcn-dou-analysis 参考选题创作(有原视频,如视频行 AI创作):传 videoId/awemeId → rewrite_log.video_id 关联原视频; 自定义选题创作(无原视频,如账号列表 AI创作 弹窗):不传 videoId → video_id 留空,归「自定义选题创作」tab。 保存走本地服务 /api/dsh/script-save:server 自行解析 mcn-plugin.db 路径(跨开发机/dsh 环境最可靠),勿让 agent 猜库路径 */ -/* 榜单数据更新技能声明(09-03 新增:热点数据页「更新榜单」按钮 → 后台任务按 mcn-data-insight 落盘规范抓取三榜最新一期。 - 范围与调用次数已在点击确认弹窗中向用户明示并获授权(账号周榜 5 赛道 + 视频热榜/点赞榜,约 7 次红狐 API); +/* 榜单数据更新技能声明(09-03 新增 09-04 四榜扩展:热点数据页「更新榜单」按钮 → 后台任务按 mcn-data-insight 落盘规范抓取四榜最新一期。 + 范围与调用次数已在点击确认弹窗中向用户明示并获授权(账号日榜 全品类 1 次 + 账号周榜 5 赛道 + 视频热榜/点赞榜,约 8 次红狐 API); prompt 内保留「查询幂等铁律」与「落盘目录按环境解析」,禁止模型逐赛道/逐日循环浪费 API 配额 */ -const SKILL_HINT_RANKING = '请调用 Skill 工具加载「mcn-data-insight」技能(技能根目录 = 用户技能目录 ~/.workbuddy/skills/短视频工作台/subskills/mcn-data-insight/,先读取其 SKILL.md 及 references 规范;若 Skill 工具无法加载,请直接读取该目录下的 SKILL.md 文件)。本任务为「榜单数据更新」(工作台热点数据页更新按钮触发,用户已确认本次调用范围与次数):按 SKILL.md「榜单数据落盘规范」将热点数据三榜最新一期数据落盘到本地 抖音榜单/ 目录:① 视频热榜 + 点赞榜:运行 scripts/fetch_week_ranks.py --board both(全品类单次调用,禁止逐赛道/逐日循环);② 账号周榜:按 subskills/douyin-top-account 技能流程抓取保留 5 赛道(生活vlog/小剧场/亲子/美食/旅行)最新一期账号周榜,逐赛道写入 账号周榜/抖音周榜_{赛道}_{周一}.json;③ 落盘目录按技能规范按环境解析(开发机/用户环境 = 桌面 MCNSkill项目\\三方数据\\红狐数据\\抖音榜单\\;dsh 环境 = D:\\dshworkspace\\抖音榜单\\),新产物入 账号周榜/视频热榜/点赞榜 三个子目录,文件名保持 抖音周榜_ 前缀;④ 查询幂等铁律:每次调用前先 ls 目标目录,同榜名+赛道+数据日期文件已存在则直接跳过对应 API 调用(红狐 API 按次计费);⑤ 数据日期取 API 返回的榜单对应日期(最新一期),非获取时刻。完成后汇报:三榜各写入的文件路径与数据期、缓存命中跳过项、实际 API 调用次数。技能边界:只做榜单数据抓取与落盘,不做账号分析/脚本创作等其他能力。'; +const SKILL_HINT_RANKING = '请调用 Skill 工具加载「mcn-data-insight」技能(技能根目录 = 用户技能目录 ~/.workbuddy/skills/短视频工作台/subskills/mcn-data-insight/,先读取其 SKILL.md 及 references 规范;若 Skill 工具无法加载,请直接读取该目录下的 SKILL.md 文件)。本任务为「榜单数据更新」(工作台热点数据页更新按钮触发,用户已确认本次调用范围与次数):按 SKILL.md「榜单数据落盘规范」将热点数据四榜最新一期数据落盘到本地 抖音榜单/ 目录:① 视频热榜 + 点赞榜:运行 scripts/fetch_week_ranks.py --board both(全品类单次调用,禁止逐赛道/逐日循环);② 账号日榜:按 subskills/douyin-top-account 技能流程抓取最新一期账号日榜——用 fetch_rank.py --period day 直接获取全品类(不传赛道,单次调用),将结果写入 账号日榜/抖音账号日榜_{榜单日期}.json;③ 账号周榜:按 subskills/douyin-top-account 技能流程抓取保留 5 赛道(生活vlog/小剧场/亲子/美食/旅行)最新一期账号周榜,逐赛道写入 账号周榜/抖音周榜_{赛道}_{周一}.json;④ 落盘目录按技能规范按环境解析(开发机/用户环境 = 桌面 MCNSkill项目\\三方数据\\红狐数据\\抖音榜单\\;dsh 环境 = D:\\dshworkspace\\抖音榜单\\),新产物入 账号日榜/账号周榜/视频热榜/点赞榜 四个子目录,文件名保持 抖音账号日榜_/抖音周榜_ 前缀;⑤ 查询幂等铁律:每次调用前先 ls 目标目录,同榜名+赛道+数据日期文件已存在则直接跳过对应 API 调用(红狐 API 按次计费);⑥ 数据日期取 API 返回的榜单对应日期(最新一期),非获取时刻。完成后汇报:四榜各写入的文件路径与数据期、缓存命中跳过项、实际 API 调用次数。技能边界:只做榜单数据抓取与落盘,不做账号分析/脚本创作等其他能力。'; +/* 账号数据分析技能声明(09-04 新增:账号详情「数据分析」tab 空态按钮 → 后台任务按 mcn-dou-analysis 功能四执行。 + 功能四=基于近 4 个月视频数据(每月最好/最差各 1 条)先逐条拆解再汇总分析,产出 8 章结构报告; + 数据源优先读取产出目录短视频表格 xlsx,缺失则以工作台库中视频数据为准(不重新抓取账号) */ +const SKILL_HINT_ANALYZE = '请调用 Skill 工具加载「mcn-dou-analysis」技能(技能根目录 = 用户技能目录 ~/.workbuddy/skills/短视频工作台/subskills/mcn-dou-analysis/,先读取其 SKILL.md 及 references 规范;若 Skill 工具无法加载,请直接读取该目录下的 SKILL.md 文件)。本任务为技能功能四「分析视频数据(账号数据分析)」:先读 SKILL.md「功能总览」定位功能四,再按 references/feature/04_分析视频数据.md 与 references/账号数据分析方法.md 执行——基于该账号近 4 个月视频数据(每月按 点赞数+分享数 降序取最好 1 条 + 最差 1 条,共最多 8 条;某月不足 2 条则全取),先逐条拆解(可复用该账号已有 MCP 解析 analysis,缺解析的按规范提交补齐)→ 拆解全部完成后再进入汇总分析(两段式强制,禁止跳过拆解直接写报告),按 3.1-3.6 维度产出 8 章结构报告。数据来源:优先读取该账号产出目录的 短视频表格*.xlsx(13 列标准格式);文件缺失时以工作台库中该账号视频数据为准(无需重新抓取账号信息/视频列表,属数据更新场景时按规范处理)。红线:所有分析结论必须具体到视频标题 + 数值(点赞/评论/分享/收藏等),私有数据(完播率/GMV 等)拿不到时标注「无数据」禁止推断;无 MCP 解析的视频只做数据层分析、禁止作为内容层结论论据(注明「无解析,植入方式待验证」)。产物落盘到该账号产出目录 {达人昵称}账号数据分析.md;落盘后调用 POST /api/import/account(body {"account":"{达人昵称}"})同步入库(工作台环境 http://localhost:8900,接口不可用则跳过不影响保存)。技能边界:只做账号数据分析报告,不生成/改写脚本(属「短视频工作台」技能 S1-S11 流程),不提炼账号设定,不触发账号信息/视频列表抓取流程。'; const saveHintOf = (ctx) => { const ref = Number(ctx.videoId) > 0; const lines = []; @@ -508,24 +512,26 @@ async function confirmAiScript(accountName, btn) { inputEl.focus(); } -/* ================= 热点数据(#/ranking:账号周榜 / 视频热榜 / 点赞榜 三榜,board 参数驱动,与首页 rank-preview 同构) ================= */ +/* ================= 热点数据(#/ranking:账号周榜 / 账号日榜 / 视频热榜 / 点赞榜 四榜,board 参数驱动,与首页 rank-preview 同构) ================= */ async function renderRanking(view) { - // 数据源:RANKING_DIR 下周榜族 JSON(fetch_week_ranks.py 落盘)——账号周榜 抖音周榜_{赛道}_{周一}.json, - // 视频热榜 抖音周榜_视频热榜_{赛道}_{周一}.json,点赞榜 抖音周榜_点赞榜_{赛道}_{周一}.json;后端按 board 识别 + // 数据源:RANKING_DIR 下周榜族 JSON(fetch_week_ranks.py / douyin-top-account 落盘)——账号周榜 抖音周榜_{赛道}_{周一}.json, + // 账号日榜 抖音账号日榜_{日期}.json(全品类单文件,抓取不传赛道一次取全),视频热榜 抖音周榜_视频热榜_{赛道}_{周一}.json, + // 点赞榜 抖音周榜_点赞榜_{赛道}_{周一}.json;后端按 board 识别 const RANK_TABS = [ ['week', '账号周榜'], + ['day', '账号日榜', '每日更新,回溯 7 天'], ['hot', '视频热榜', '近7日数据'], ['likes', '点赞榜', '近7日数据'], ]; const mkState = () => ({ date: '', category: '', sortBy: '', order: '', page: 1, pageSize: 20, dates: [], categories: [] }); // 每榜独立状态(切 tab 保留各自筛选/翻页/日期) - const st = { week: mkState(), hot: mkState(), likes: mkState() }; + const st = { week: mkState(), day: mkState(), hot: mkState(), likes: mkState() }; let tab = 'week'; const tabLabel = () => { const t = RANK_TABS.find(([k]) => k === tab); return t ? t[1] : ''; }; // 09-03 page-toolbar 只放 返回+标题;rank-toolbar 恒渲染含 tab + 下载按钮(紧贴 tab 右侧)保证空态/错误态也能看到更新入口(首次抓取);筛选+翻页仅 withFilters 时出现在同行最右 const toolbar = (s, withFilters) => { const tabsHtml = `${RANK_TABS.map(([k, label, tip]) => ``).join('')} - `; + `; const filtersHtml = withFilters ? `