diff --git a/.gitignore b/.gitignore index bc5d7ae..7323972 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,10 @@ Thumbs.db **/browser-harness/envs/ # WorkBuddy 自动化任务运行数据(本地运行时生成,不提交) .workbuddy/automations/ +# 工作台运行日志与 CDP 自检截图(本地产物,不提交) +**/mcn-work-shop/server.log +**/mcn-work-shop/shot-*.png +# 临时目录与生成产物 +tmp/ +.workbuddy/tmp/ +generated-images/ diff --git a/.workbuddy/memory/2026-08-28.md b/.workbuddy/memory/2026-08-28.md index 7269706..540f585 100644 --- a/.workbuddy/memory/2026-08-28.md +++ b/.workbuddy/memory/2026-08-28.md @@ -160,3 +160,22 @@ - **验证**(CDP #/rewrites):按钮在 AI脚本 head 内、与文字 gap=10px 精确、垂直对齐、顶栏无残留 ✅;点击展开源脚本列 + title 切「收起源脚本」✅;宽度 1200→1085 / 1500→1380 / 1700→1564 / **2000→1600 封顶居中** ✅ - ⚠️ 左右边距探针:`right:65` 不是 bug——mask(fixed inset:0) 宽=clientWidth 1185(不含滚动条),弹窗 calc(100%-100)=1085 在 mask 内精确居中左右各 50;15px 是页面右侧 classic 滚动条区域,滚动条左缘到弹窗恰 50px;无滚动条页面左右严格 50/50 - 验证脚本保留 `cdp-verify-srcbtn1600.mjs` + +## 关闭服务 + 以本地为准同步仓库(18:51-18:55) +- **关闭服务**:工作台 node 服务 PID 28424(127.0.0.1:8899)taskkill 停止;0.0.0.0:8899 的 20604 是 MiPCAudio.exe 占位不动;Git Bash `taskkill //PID` 参数会被 MSYS 转换报错,改 PowerShell `Stop-Process` +- **提交**(ab97842,155 文件):工作台导入功能/弹窗优化 + **短视频提示词生成 138 文件整体移入 `project/短视频脚本创作/V1.0/subskill/mcn-video-prompt/`**(git 识别为 R100 纯重命名保留历史,与 mcn-dou-analysis 同构);未提交项有意排除:`.workbuddy/tmp/`(edge-cdp 调试临时)、`server.log`、`cdp-wheel*.mjs`(旧滚轮探针) +- **以本地为准推送**(用户明确「不用拉取 直接同步」):`git pull` 走 22 端口超时 + 沙箱拦截 ~/.ssh 读取被用户拒 → 改用 `GIT_SSH_COMMAND="ssh -p 222 -o StrictHostKeyChecking=accept-new" git push origin main` ✅ fast-forward 成功(9b0c198..ab97842),**这次 push 后 origin/main 跟踪引用未丢失**(refs/remotes/origin/main 正常) + +## 账号周榜 5 数值列加宽(17:56-18:00) +- 用户需求:「账号周榜 列表中 下面的列宽度可以在大一些:综合得分 / 新增粉丝 / 新增点赞 / 新增评论 / 新增分享」 +- **改 data-pages.js**(renderRanking):`const WIDE = new Set(['comprehensiveScore','newFans','newLikes','newComments','newShares'])` → `sortable()` 工厂给命中 key 的 th 追加 `th-wide` class +- **改 style.css**(`.sortable:hover` 后):`.th-wide { min-width: 110px; }`(表格为 auto 布局,数值列加 min-width 即可增宽,不影响内容列) +- **验证**(CDP #/ranking 只读测量):5 列均带 th-wide、实测宽 **122px ≥110px** ✅、computed min-width 110px;对照列不受影响(rank 56 / 赛道 90 / followers 98 / 功能 77);20 行数据正常渲染 +- 验证脚本保留 `cdp-verify-rankwide.mjs`(含列宽测量 + computed style 校验,可复用) + +## 旧梦留声机账号设定卡 + 功能六固化(17:50-18:00 前段多轮迭代,本段收尾) +- **SVG 迭代定版**:`桌面/MCNSkill项目/旧梦留声机/旧梦留声机账号设定卡.svg`,viewBox `0 0 680 1716` 深棕红渐变底(`#2B0F0A→#1A0805`)+ 顶部浅米大框(280px 主标题+16px 副题「账号设定卡」)+ 内容路线 6 格浅米卡 + 核心气质 12 chips + 六大内容基因 186×130(GENE 0X 珊瑚红小标签)+ 色彩体系 5 色卡 h=80 + 角色类型库 186×130(中文名+英文代号大写斜体)+ 情绪图条形(76/32/64/112/84/52/72 底边对齐 y=1452)+ 小字总结行 y=1506 + 红线清单 6 条 2×3 + 版权 +- **功能六固化(生成账号设定卡片)**:新建 `references/feature/06_生成账号设定卡片.md`(输入/产出/五步方式含①.5读模板样例/模块裁剪适配表/配色适配/要求配色规范表/格式/7条避坑/完成汇报)+ `references/样例/`(模板_旧梦留声机账号设定卡.svg + 参考PNG,源+dsh 两副本)+ SKILL.md 功能总览五大→六大 + 新增「八、功能六」章节 + 部署禁用说明(功能六无连带文件) +- **收尾完善(本次)**:①SKILL.md 2.2 技能库路径表补录「功能六流程」+「账号设定卡模板样例」两行(源+dsh);②`R. REPayer`→`R. REPAYER` 大小写统一(模板源/dsh/桌面成品三处,其余代号全大写) +- **验证**:源技能 vs dsh 副本 SKILL.md/模板SVG/06功能文件三组 diff 全一致;REPayer 全库零残留 +- **沉淀要点**(供下次生成其他账号设定卡):先读 `references/样例/` 模板(复用版式骨架与样式常量,内容全替换);角色英文代号=大写首字母缩写+单词全大写;情绪图条形底边对齐峰谷差≥40px;文字垂直居中 y=中心;批量移动坐标用脚本统一加减防双重偏移;红线全量;改完必 present_files 预览确认 diff --git a/.workbuddy/memory/2026-08-31.md b/.workbuddy/memory/2026-08-31.md new file mode 100644 index 0000000..ceee497 --- /dev/null +++ b/.workbuddy/memory/2026-08-31.md @@ -0,0 +1,337 @@ +# 2026-08-31 工作日志 + +## 工作台首页顶部「数据预览」条带(09:34-09:50) +- 用户需求:「工作台首页顶部增加数据预览 参考插件MCN工作台首页顶部的数据预览」→ 参考源确认 = `D:\dshworkspace\plugin_package\dsh-plugin-mcn`(本机源码唯一含 mcnNav_stats 的实现,非线上 agentos) +- **参考实现**(dsh-plugin-mcn client.js + index.js):首页 home 视图顶部 stats 条带(浅蓝底 rgba(65,118,230,.07)、圆角 12、padding 22px 0),5 项 statItem(flex:1 竖排 value 28px 粗体品牌蓝 + label 13px 次级色、item 右侧细边框分隔、末项无边框);数据 `GET /mcn/api/stats`(后端 60s 缓存):accounts=hot_accounts del_flag=0 / analyses=account_video_analysis 计数 / rewrites=rewrite_log done+script_text 非空 / reviews=script_review type='ai'+done / writingScore=最新 20 条 AI 复盘 total 平均(1 位小数) +- **移植改动**:①dsh-data.js `getStats()` 修正对齐参考——reviews 补 `type='ai'` 限定、writingScore 由「计数」改为「最新 20 条 AI 复盘平均分」(无数据返回 null);②app.js renderHome `.home` 顶部(data-cards 前)加 `#homeStats` 条带占位 + 新增 `loadHomeStats()`(挂载 fetch /api/dsh/stats 一次,5 项 label 与 dsh 一致,失败显示「数据不可用」)+ router home 分支与 _reload 调用;③style.css 新增 `.home-stats/.home-stat-item/.home-stat-value/.home-stat-label`(工作台品牌色 --primary #2f6fed 对应 dsh 的 #4176e6,背景 rgba(47,111,237,.07)) +- **验证**(CDP #/ 强制刷新):5 项渲染值 热门账号7/视频解析12/AI写脚本17/AI写作复盘1/AI写作评分86.5 与接口一致 ✅;样式 computed 全符合(bg .07 蓝、圆角 12、值 26px 700 品牌蓝、item 右分隔线、末项无)✅ +- ⚠️ **坑**:CDP `Page.navigate` 到仅 hash 不同的 URL(#/ranking→#/)**不触发文档重载**,CSS/JS 仍是旧版(表现为新增样式全部 computed 为默认值)→ 必须补 `Page.reload({ignoreCache:true})`;服务端 dsh-data.js 改动需**重启 node 服务**(进程级 require 缓存) +- 验证脚本保留 `cdp-verify-homestats.mjs`(结构+值+computed 样式测量,可复用) + +## 首页账号周榜预览表:5 数值列加宽 + 功能列「分析」→「关注」(09:42-09:55) +- 用户澄清:要加宽的是**工作台首页的账号周榜预览表**(非 #/ranking 功能页——上轮 08-28 加的是功能页,本轮补首页预览表) +- **改 style.css**(`.rank-row` grid):`grid-template-columns: 34px 84px minmax(80px,1fr) 52px repeat(4,58px) 72px` → `... 88px repeat(4,84px) 72px`(综合得分 52→88、4 个数值列 58→84) +- **改 app.js**(loadRankingPreview 行模板):非 inDb 账号操作按钮「分析」→「关注」(title「分析该账号并加入账号列表」→「关注该账号并加入账号列表」;行为不变仍 confirmSaveAccount 保存并分析);inDb 账号保持「查看」 +- **验证**(CDP #/ 强制刷新实测):5 列宽 score=88 / 4×rk-num=84 ≥80 ✅;按钮「关注」+ title 正确 ✅;10 行数据正常、首页 stats 条带与快捷卡片不受影响 ✅ +- ⚠️ 验证脚本坑:flex/grid 行内同类元素取列宽用 `:nth-child(n)`(按子元素序号),**不能用 `:nth-of-type`**(按元素标签类型 span 计数会错位);rk-num 是行内第 5-8 个子元素 +- 验证脚本保留 `cdp-verify-rankpreview.mjs` + +## 账号周榜全部页面数值列「窄 + 右对齐」(09:50-10:05) +- 用户需求:「账号周榜 全部页面 这些列宽度可以窄一些 右对齐:综合得分/粉丝/新增粉丝/新增点赞/新增评论/新增分享」 +- **功能页 #/ranking**:①data-pages.js WIDE 集合补 `followers`(6 列)、数值列 td 加 `num` 类(综合得分 `num score-cell`);②style.css 根因排查——`width:100%` auto 布局下列需求总和(≈994px) < 容器(1440px),富余空间按比例摊薄拉宽每列,**min-width 只是下限压不住拉伸**(实测 110px 是摊薄结果,thMin 已 90px 却无效)→ 解法:周榜表单独 `table-layout: fixed` + 数值列 `width:90px` 硬约束(fixed 下 width 生效、min-width 忽略),排名 40/赛道 90/功能 85 固定,账号列吃剩余;table 加专属类 `table-rank`(避免影响账号列表等其他 .table 页面);td.num 缩 13px + tabular-nums + padding 8px +- **首页预览表**:`.rank-row` grid 列宽 88/84 → **76/76**(grid 本来就是硬约束,仅缩窄;右对齐原本就有) +- **验证**(CDP 实测):功能页 6 列全部 **90px + th/td 右对齐** ✅;首页 5 列 76px 右对齐 ✅;行数据/账号列自适应正常 +- ⚠️ 两个坑:①`.table th { text-align:left }` 特异性(0,1,1) 压过 `.th-wide`(0,1,0),需写 `.table th.th-wide`;②auto 布局富余空间摊薄是「列宽压不住」的真根因,fixed + width 才是硬约束 +- 验证脚本保留 `cdp-verify-ranknarrow.mjs`(功能页+首页双测) + +## 「AI写作复盘」→「AI脚本诊断」改名 + 首页周榜列表上底色(09:56-10:08) +- 用户需求:①「AI写作复盘」包括功能入口和数据预览名称都改为「AI脚本诊断」;②账号周榜列表内容区域上底色(原来透明与页面背景 #f5f6f8 同色很奇怪) +- **改名 7 处全量替换**(app.js 6 + data-pages.js 1):首页快捷卡片 title('flask')、数据预览条带 label(占位 HTML + loadHomeStats 两处)、CRUMB_TITLES、router titles、reviews 页 page-title、代码注释;index.html 无「复盘」字样;「AI写作评分」按用户范围保持不改;页面内部功能描述(复盘报告等)保持 +- **底色**:`.rank-preview-list` 补 `background: var(--panel); box-shadow: var(--shadow);`(白色面板 + 阴影,与快捷卡片一致;功能页 .table-wrap 本就有 panel 白底) +- **验证**(CDP 实测):数据预览 label 与快捷卡片均为「AI脚本诊断」✅;全页无「AI写作复盘」残留 ✅;周榜列表背景 rgb(255,255,255) ✅;#/reviews 标题「AI脚本诊断」✅ +- 验证脚本保留 `cdp-verify-rename.mjs` + +## 周榜功能列按钮统一白底带边框(09:58-10:10) +- 用户需求:「账号周榜 功能列的按钮都改为 白底带边框」 +- **改 style.css** `.btn-import`(功能页「分析」+ 首页「关注」共用):灰底 #f0f2f5 → **白底 #fff + 边框 #d0d4da**,hover 变主色边框+主色字;`.btn-view`(查看)本就是白底+主色边框未动 +- **验证**(CDP 实测):功能页「分析」白底 rgb(255,255,255)+边框 #d0d4da ✅;首页「关注」同 ✅ +- 验证脚本保留 `cdp-verify-btnwhite.mjs` + +## 首页周榜预览改 tab 切换 + 新增 3 个空榜(10:02-10:15) +- 用户需求:「首页账号周榜 改为 tab切换,新增 作品热榜、点赞日榜、涨粉日榜,功能先空着」 +- **改 app.js renderHome**:`

账号周榜

` → `.rank-tabs` 4 个 `.rank-tab`(账号周榜 week 默认 active + 作品热榜 hot + 点赞日榜 likes + 涨粉日榜 fans),工具条保留(id=rankTools);内容区新增隐藏的 `#rankPlaceholder` 占位块 +- **改 app.js router home 分支**:`.rank-tab` 点击切换 active;week → 显示 #rankTools+#rankPreviewList、隐藏占位;其余 → 隐藏工具条和列表、占位显示「『XX』建设中,敬请期待」 +- **改 style.css**:`.rank-tabs/.rank-tab/.rank-tab.active/.rank-placeholder`(胶囊 tab:白底边框,active 主色蓝底白字;占位:虚线框+白底居中灰字) +- **验证**(CDP 实测):4 tab 初始状态 ✅;逐个点击 3 新榜 → 工具条/列表隐藏 + 占位文案正确 ✅;点回账号周榜恢复列表+筛选 ✅ +- 验证脚本保留 `cdp-verify-ranktabs.mjs` + +## 首页周榜 tab 选中样式:深蓝胶囊 → 浅底色 + 下划线(10:16-10:25) +- 用户反馈:「tab 能不能不要用 深蓝色选中,这是哪来参考的样式,太抢眼了,就用下划线效果 浅底色就行」 +- 首版 `.rank-tab.active` 沿用顶部主导航 `.tab` 的深蓝胶囊样式(白字+深蓝底 #2f6fed),用户嫌抢眼 +- **改 style.css**:`.rank-tabs` 补 `border-bottom:1px solid var(--border)`(tab 栏底部分隔线,浅色方案基础);`.rank-tab` 透明背景 + `border:none` + `border-bottom:2px solid transparent`(下划线占位防切换抖动)+ 次级色文字;`.rank-tab.active` = `background:var(--primary-soft)`(#eaf1fe 浅蓝底)+ `border-bottom:2px solid var(--primary)`(主色下划线)+ 主色 600 字重;hover 同浅底主色 +- **验证**(CDP 强制刷新实测):active computed bg=rgb(234,241,254) / color=rgb(47,111,237) / border=2px solid rgb(47,111,237) / weight=600 ✅;非 active 3 个 tab 全透明背景 + 透明下划线占位 + 次级色文字 ✅;4 tab 切换/回切 active 样式均正确 ✅ +- ⚠️ 验证脚本坑:querySelector 传入元素对象(不是选择器字符串)会报 `'[object HTMLButtonElement]' is not a valid selector` +- 验证脚本保留 `cdp-verify-tabstyle.mjs` + +## 首页周榜 tab 改名(10:10-10:15) +- 用户需求:「把点赞日榜 和 涨粉日榜 去掉日,作品热榜 改为 视频热榜」 +- **改 app.js renderHome**:tab 按钮文本 `作品热榜`→`视频热榜`、`点赞日榜`→`点赞榜`、`涨粉日榜`→`涨粉榜`(仅文案,`data-rank-tab` 值 hot/likes/fans 不变) +- router 切换按 `data-rank-tab` 匹配 + 占位文案动态取 `t.textContent`,故只改按钮文本、占位自动跟随 +- **验证**(CDP 强制刷新实测):4 tab 名称 账号周榜/视频热榜/点赞榜/涨粉榜 ✅;逐一点击占位文案「视频热榜/点赞榜/涨粉榜」建设中 ✅;回切账号周榜恢复列表 ✅ + +## 账号列表「导入」→「添加」+ 弹窗精简(10:13-10:22) +- 用户需求:「账号列表中 导入功能名称 改为添加,点击按钮弹窗 名称改为 添加账号数据,输入项去掉 方式二和抖音号输入」 +- **改 data-pages.js**:accImport 按钮「导入」→「添加」,title「添加账号数据(抖音账号信息 / 账号地址)」 +- **改 app.js openImportAccounts**:弹窗标题「添加账号数据」;删除方式二整块(本地文件夹路径输入 + data-imp-dir)与抖音号输入(data-imp-did);说明文字去「方式一:」前缀(只剩一种方式);提交按钮「提交导入」→「提交添加」;提交逻辑清理——去掉 dir/did 分支,校验改「请填写账号名称或账号地址」,prompt 只含名称/地址,executeTask label '导入指令'→'添加指令' +- **改 style.css**:注释「导入账号弹窗:方式一账号信息 / 方式二本地文件夹」→「添加账号弹窗:账号信息(名称/地址)」 +- 保留:账号详情页 infoGrid「抖音号」展示字段(非输入项)、帮助页「导入账号」自然语言用法(用户未要求) +- **验证**(CDP #/accounts 强制刷新实测):按钮「添加」+ title ✅;弹窗标题/2 个输入项(账号名称+账号地址可选)/提交添加/无方式二·文件夹·抖音号 ✅ +- ⚠️ 验证坑:`document.querySelector('.modal')` 取到的是**页面加载时自动弹出的「项目文件地址」设置弹窗**(index.html 既有行为,非本次引入)→ 需取最后一个 `.modal-mask` 才是新开的弹窗 + +## 添加按钮图标:download → user-plus(10:15-10:18) +- 用户需求:「按钮前面 图标也要改」——「添加」按钮前是下载图标(导入语义残留),需换贴合「添加账号」的图标 +- **改 app.js**:ICON_PATHS 新增 `'user-plus'`(Lucide 标准:人像 + 右下加号);弹窗标题图标 `svgIcon('download', 18)` → `svgIcon('user-plus', 18)` +- **改 data-pages.js**:accImport 按钮图标 `svgIcon('download', 15, 'currentColor')` → `svgIcon('user-plus', 15, 'currentColor')` +- **验证**(CDP 实测):按钮/弹窗标题图标均为 user-plus、无 download 残留 ✅ +- ⚠️ 验证坑:查弹窗图标前必须先 click 打开(否则取到的是页面自动弹出的设置弹窗,无 h3) + +## AI写脚本页操作列按钮:「AI改写」→「AI写脚本」+ 样式统一(10:19-10:22) +- 用户需求:「AI写脚本 列表页 操作列改名功能,按钮样式参考 账号列表中功能列按钮样式」 +- **背景**:账号列表(#/accounts)操作列按钮本就是「AI写脚本」(`btn btn-sm` 白底灰边框 → confirmAiScript);AI写脚本页(#/rewrites)操作列却是「AI改写」(`btn btn-sm btn-view` 主色边框 → confirmRun 改写),两处语义不一致 +- **改 data-pages.js renderRewrites 行模板**:`` → ``(文案统一 + 去掉 btn-view 改白底灰边框,与账号列表操作列一致) +- **验证**(CDP #/rewrites 强制刷新实测):按钮文本「AI写脚本」✅、class=btn btn-sm 无 btn-view ✅、白底 rgb(255,255,255)+灰边框 #e3e6ea+灰字 ✅(20 行全部生效) +- 未改:视频详情页(298 行)与账号详情页的「AI改写」按钮(用户仅指定 AI写脚本列表页)、弹窗标题「改写脚本」 + +## 旧梦留声机账号设定卡(SVG 信息图,10:23-10:30) +- 用户需求:参考《权游》影视需求 Skill 信息图,用「旧梦留声机」账号设定生成同款设定卡 +- **数据源**:`C:\Users\maidou\Desktop\MCNSkill项目\旧梦留声机\旧梦留声机账号设定.md`(群像账号:六内容基因/六段式情绪核弹/角色类型库6类/红线6条/色彩体系/内容路线六步闭环) +- **产出**:`旧梦留声机账号设定卡.svg`(同目录,680×990 竖版信息图,show_widget 同步内联渲染) +- 结构映射(对齐权游 12 模块):标题+类型+情绪锚点 → 内容路线6步流程条 → 核心气质8chips → 六大内容基因6卡 → 色彩体系5色卡(泛黄旧时光/冷灰乡土/暖黄回忆/冷白危机/明亮都市)→ 角色类型库6卡(含记忆符号)→ 六段式情绪核弹结构(柱状曲线+时长占比)→ 红线清单6条 +- 视觉语言:暖黄旧时光色系(c-amber #FAEEDA/#854F0B/#633806 主 + c-coral #FAECE7/#993C1D/#712B13 点缀),核爆段 coral 600 高亮;账号美学对齐(非权游深色) +- 用户可继续要求:其他账号同款卡 / 调整模块 / 导出 PNG + +## 旧梦留声机设定卡·情绪图条形加高(10:34-10:36) +- 用户需求:「情绪图能否整体高度再高一些,各阶段情绪起伏明显」 +- **改** `桌面\MCNSkill项目\旧梦留声机\旧梦留声机账号设定卡.svg` 六段式情绪核弹结构:7 条条形底边统一对齐 y=812(原 804),高度放大——谷底「绝境施压」14→16、峰顶「核爆段」38→**56**,峰谷高差 24→40px;全部条顶 y 上移,注释行 y 822→836(与下方红线清单标题 y852 无重叠) +- 验证:条形文字垂直居中仍贴合条形内;峰谷对比明显 + +## 旧梦留声机设定卡·长图版配色升级(10:35-10:40) +- 用户需求:「长图版 标题颜色,卡片背景和文字颜色 都更好看」 +- **重写** `旧梦留声机账号设定卡.svg` 配色体系(布局坐标不变): + - 标题:主标题改**渐变** `#8C2F1E→#C05A32` + weight 700;分隔线琥珀金 #E0953C;副标题 #B4642F;标语 #8C2F1E + - 模块:标题统一 #5E2A1E 600 + 竖条珊瑚红 #C13B2F;卡片双色系交替——暖奶油 #FFF3E2+边框 #EDCBA4(路线/基因/色彩)与浅珊瑚奶油 #FDEBE2+边框 #E8B7A2(气质/角色/红线) + - 文字:卡片标题 #7B2D1B/#8C3B2B,正文暖灰 #6F5B4A(替代原 #5F5E5A 纯灰);情绪图核爆 #D6403A+白字600,琥珀系统一 #F0B25C/#F7D9A8;红线×号 #C13B2F 600 + - 整图加暖米底 #FDF8F0(原透明) + +## 旧梦留声机设定卡·情绪图条形高度翻倍(10:37-10:42) +- 用户需求:「情绪部分 高度继续增加 一倍」 +- **改** `旧梦留声机账号设定卡.svg`:7 条条形高度全部 ×2(38→76/16→32/32→64/56→112/42→84/26→52/36→72),底边统一对齐 y=880(原 812);因条形变高,注释行 836→904、红线清单整体下移(标题 852→932,卡片行 872/910/948→952/990/1028),viewBox 高度 990→1080 +- 验证:高度×2 正确、底边全对齐 880、文字仍居中(核爆 768+56=824) + +## 旧梦留声机设定卡·基因卡/角色卡排版升级(10:39-10:45) +- 用户需求:六大内容基因与角色类型库卡片文字排版按「GENE 01 标题 内容」「角色中文 英文 内容」样式 +- **六大内容基因卡**:标题行拆为「GENE 0X」珊瑚红 #C13B2F 10px 600 小标签(x=61/259/457)+ 标题 12px 600 #7B2D1B(x=118/316/514),内容两行不变 +- **角色类型库卡**:标题行拆为「中文名」12px 600 #8C3B2B(x=61/259/457)+ 英文名 9px #B0715A **右对齐**(text-anchor=end,x=223/421/619),内容行不变;英文名=Elder Guardian/Repaying Child/Suffering Mother/Great-Love Prototype/Guiding Benefactor/Stubborn Dreamer +- 验证:SVG 结构完整(1 svg/8 g/53 rect/97 text)、6 GENE + 6 英文名就位、全部标题末字尾 ≤ 卡右缘-8 无溢出 + +## 旧梦留声机设定卡·按参考长图版整体重写(10:44-10:55) +- 用户反馈:「排版还是不一样呢,仔细看看之前生成 长图版」+ 提供参考图 `桌面\旧梦留声机_账号设定卡_长图版.png` +- **重写** `旧梦留声机账号设定卡.svg`(完全推翻前几版,viewBox 680×1560): + - 背景:深棕红渐变 `#2B0F0A→#1A0805`(原暖米底) + - 顶部大框:浅米色 520×170 圆角 14,框内"旧梦留声机 · 账号 / 设定卡"两行 26px 渐变大标题 + 琥珀金分隔线 + 副标题 + 标语 + - 装饰线:居中 `— channels requirement board 2026 —` + - 模块标题:▍+ 浅米色 14px 加粗 + - **六大内容基因卡**(186×130,原 72):GENE 0X 标签 letter-spacing 1 + 大标题 14px 700 + 3 行描述(含新增金句"让他撑下去"/"脏一点泪点多一点"等) + - **角色类型库卡**(186×130,原 48):PROTOTYPE 标签 + 中文名 14px 700 + **英文代号大写斜体** `E. ELDER / R. REPayer / M. SUFFERING / A. ARCHETYPE / G. GUIDE / D. DREAMER` + 2 行描述 + - 红线清单:深色 `#3D1810` 卡 + 边框 + 红×号 + - 底部版权:数据来源 + content requirement board + +## 旧梦留声机设定卡·修复红线清单与情绪图高度(10:47-10:52) +- 用户反馈:红线清单内容少了(重写长图版时只保留 2 条)、情绪部分高度又变矮了(重写时条形缩水) +- **修复** `旧梦留声机账号设定卡.svg`: + - 红线清单 2 条 → 补全 **6 条**(消费苦难/伪造真实素材/美化悲剧/角色没名字/强行好结局/植入打断情绪,2 列×3 行,卡 y=1538/1576/1614) + - 情绪图条形恢复到翻倍高度:56→76/26→32/44→64/76→112/54→84/34→52/46→72,底边对齐 y=1452(核爆段 y=1340 与标题基线 1336 差 4px 不重叠) + - viewBox 1560→1700,底部版权下移至 1672/1684 +- 验证:7 条底边全对齐 1452、红线 6 条、无重叠 + +## 旧梦留声机设定卡·去 PROTOTYPE + 情绪小字(10:48-10:53) +- 用户反馈:① 角色类型库不需要加 PROTOTYPE 这个英文 ② 参考图情绪部分下面有一行小字 +- **修改** `旧梦留声机账号设定卡.svg`: + - **去掉 6 张角色卡的 PROTOTYPE 标签**(珊瑚红小标签)→ 角色卡排版简化为「中文名 + 英文代号 + 3 行描述金句」(描述从 2 行扩到 3 行,新增金句"沉默里把全家扛在肩上"/"朴素到极致是最贵的善良"/"指路的手势比讲理更动人"等) + - **情绪图下方加一行小字** y=1506:`情绪节奏总览:压抑蓄力 → 核爆释放 → 破壁落地,每 10-30 秒完成一次情绪反转`(11px 斜体浅米色 #D9BFA3,居中) +- 验证:PROTOTYPE 残留 0,6 中文名+6英文代号完整,小字行已嵌入 + +## 旧梦留声机设定卡·标题换行+内容路线颜色(10:49-10:53) +- 用户反馈:①「旧梦留声机 · 账号 / 设定卡」换行不好看 → 改「旧梦留声机」+「账号设定卡」两行(26px 渐变标题,不带点号)② 内容路线参考图不是深色 → 卡片由 #3D1810 深色改 **#FAEEDA 浅米色 + 边框 #EDCBA4 + 文字 #7B2D1B**(与参考图/基因卡同色系) +- 验证:标题两行、内容路线 6 卡填充/边框统一浅米色 + +## 旧梦留声机设定卡·标题大小层级(10:51-10:54) +- 用户反馈:「账号设定卡 这几个字明显该小一些」 +- **改** `旧梦留声机账号设定卡.svg` 顶部标题:「旧梦留声机」28px 700 渐变 +「账号设定卡」**16px 600 #8C2F1E letter-spacing 2**(副标题级,缩小 12px),形成大小主次 + +## 旧梦留声机设定卡·垂直居中+间距修复(10:52-10:56) +- 用户反馈:① 内容路线卡片中文字没有垂直居中 ② 红线清单和情绪下面的文字靠在一起了 +- **改** `旧梦留声机账号设定卡.svg`: + - 内容路线 6 个文字 y 326→**321**(=卡片中心 296+25,垂直居中) + - 红线清单整体下移:标题 1522→1538、红线卡 1554/1592/1630、版权 1688/1700;情绪小字(y1506)与红线标题间距 16→**32px**;viewBox 1700→1716 +- 验证:6 文字全部居中、间距 32px、红线 3 行 1554/1592/1630、viewBox 1716 + +## 旧梦留声机设定卡·色彩体系卡片加高(10:53-10:58) +- 用户反馈:「色彩体系 卡片 参考图中也高一些」 +- **改** `旧梦留声机账号设定卡.svg`:色彩卡高度 50→**80**(y=882 h=80,标签文字 954→984);角色类型库整体下移 10px(标题 1000→1010,卡 1018→1028/1162→1172,内部文字同步 +10) +- ⚠️ 坑:手动 Edit 改第一张卡 y 后又用脚本整块 +10,导致第一张卡双重偏移(1038),已单独修正回 1028 +- 验证:色卡 h80、角色卡 y 统一 1028/1172、色卡底 962 vs 角色库标题 1010 间距 48、角色库底 1302 vs 情绪标题 1336 间距 34 + +## 账号分析技能新增「功能六:生成账号设定卡片」(10:55-11:05) +- 用户需求:把生成账号设定卡片总结成一个功能(方式/要求/格式/注意事项),放入账号分析技能 +- **新增** `references/feature/06_生成账号设定卡片.md`(源+dsh 副本同步):输入=功能三产出 `{达人昵称}账号设定.md`;产出=`{达人昵称}账号设定卡.svg`(680×~1700 深色长图);四步方式(读设定→12段模块映射→SVG手绘→验证交付);配色规范表(深棕红渐变背景/浅米色卡片/GENE 0X 标签/角色英文代号/核爆红);注意事项含「批量移动坐标防双重偏移」等 7 条避坑 +- **改 SKILL.md**:功能总览 五大→**六大功能** + 功能六行;新增「八、功能六」章节;部署禁用说明补功能六(无连带文件);章节编号 七/八/九 连续 +- 验证:源技能与 dsh 副本 06 文件与 SKILL.md diff 一致 ✓ + +## 账号分析技能功能六·补充模板样例与适配规则(10:58-11:08) +- 用户追问:下次生成别的账号设定卡片也要达到相同效果 → 除流程规范外补充三块 +- **① 模板样例入技能**(自包含):`references/样例/模板_旧梦留声机账号设定卡.svg`(最佳成品)+ `参考_旧梦留声机_账号设定卡_长图版.png`(渲染效果);功能六「方式」新增 ①.5 步——动手前必读模板,复用版式骨架/样式常量,内容全替换;模板迭代=改模板→同步生成旧梦留声机卡确认→再用于新账号 +- **② 模块裁剪适配规则**(新章节):按设定实际内容裁剪/增补——无流程删内容路线、无角色库删角色库、基因按条数定列数(2/3列 卡宽=600px/列数-间距)、情绪按实际段数(4-7段)、色彩用真实色值、红线条数不限;每个保留模块须 grep 验证能找到原文 +- **③ 配色适配规则**(新章节):背景/卡片默认深色长图风保系列一致;色彩体系色块用账号真实色值;强调色跟随账号主色(暖→珊瑚红/琥珀,冷→冷蓝/冰青);冲突时以看得清/有层次/账号辨识度最高准则 +- 同步:源技能 06 文件+SKILL.md+样例目录 → dsh 副本全部 diff 一致 + +## MCN工作台 账号详情 persona tab 卡片/文字切换(11:00-11:35,三轮迭代) +- **用户需求**:账号详情「账号设定」tab 改为可切换「卡片版/文字版」,默认显示卡片,无卡片显示文字 +- **后端新增 2 接口**(server.js): + - `/api/dsh/account-card?name=` → 在 allowedRoots() 各根目录下查找 `{账号名}账号设定卡.svg`(规范路径 `{root}/{name}/{name}账号设定卡.svg` → 根下直挂 → 账号目录内扫描含「账号设定卡」的 svg,防目录名/账号名不一致) + - `/api/file?path=` → 内联文件输出(无 attachment 头,Content-Type 按 mimeOf),供 `` 渲染 SVG + - 新增 `findAccountCard(name)` 工具函数(安全清理 `[\\/:*?"<>|]` 非法字符防路径穿越) +- **前端 persona tab 改造**(data-pages.js renderAccountDetail): + - 状态:`viewMode='card'`(卡片版默认)+ `personaCard={path|null}` + - `loadPersona()` 改 `Promise.all([account-persona, account-card])` 并行加载 + - panel **三态渲染**:无设定→空态按钮(不变)/ 无卡片→直接文字版 `persona-text` / 有卡片→右上角图标切换组 `.persona-wrap > .persona-vs-icons + .card-view + .persona-text` + - 切换事件:直接 DOM 切换 `.card-view/.persona-text` 的 `vs-hidden` 类(不重渲染,保留滚动位置);按钮选择器 `[data-vs]` 兼容图标按钮 +- **样式三轮迭代**(style.css): + 1. 整行 segmented control「卡片版|文字版」+ 蓝底激活态(`.view-switch` 块) + 2. 改为**右上角图标按钮组**:`.persona-wrap { position:relative }` + `.persona-vs-icons { position:absolute; top:0; right:0; z-index:2; display:inline-flex }`,图标 `film`(卡片)+ `file-text`(文字),激活态蓝底白字 + 3. 卡片图片 `max-width: 540px` → **width:100% 自适应内容区域**(去 max-width,依赖父 page max-width 封顶) +- **多视口验证**(CDP Emulation 900/1024/1280/1920/2560): + - imgW = wrapW,小视口 811→中视口 1191→大视口封顶 1382 + - 所有视口 `docScrollW < viewport`,**无横向滚动** + - 用例 1629 旧梦留声机(有卡):默认卡片版、切文字版、切回卡片版 全过 + - 用例 1627 王微斯(无卡):直接文字版无切换控件 +- **服务重启**:旧 23976 任务被停(cNHaFK),新启后台任务 QMmTlH(127.0.0.1:8899);服务 Cache-Control: no-store,改完仍需 `Page.reload({ignoreCache:true})` 强制刷新 +- 验证脚本保留 `cdp-verify-personacard.mjs`(多视口自检+三态断言+截图,可复用) + +## 功能六配色适配升级:底色跟随账号风格(11:17-11:22) +- 用户反馈:「不同风格账号卡片要用对应底色,不是全部都是一个颜色」 +- **改 06 文件「配色适配」章节**(原「默认沿用深色长图风」→ 强规则「禁止一律模板深棕红底」): + - 三步判定:①读设定色彩体系真实色值+赛道判风格 ②风格→底色映射表(7 类:暖怀旧→深棕红/浅米、温情亲子→深焦糖/奶油米、科技冷淡→深灰蓝/冷白、清新三农→深墨绿/米白、潮流都市→深紫黑/浅灰白、活力运动→深海军/冷白、甜美少女→深莓紫/浅粉白) ③强调色=账号主强调色 + - 底色推导公式(未覆盖风格):主色压暗 15-25% + 降饱和 → 深色渐变底;卡片色取高对比浅色;系列一致性=同账号一致非跨账号一致 +- **改 ①.5 读模板样例**:「配色体系为基准」→「版式骨架/字号/卡片尺寸/间距为基准,配色不沿用模板」 +- **改 SKILL.md 八功能六配色适配要点**:底色跟随账号风格,禁止一律模板深棕红底 + 风格映射摘要 +- 源技能 → dsh 副本同步,06 文件与 SKILL.md diff 全一致 + +## 功能六新增「触发场景处理」章节(11:20-11:24) +- 用户问「如果用户要求生成账号设定卡片,应该如何处理 分几种情况」→ 输出 7 情况分类 + 三问判定链,用户认可后固化 +- **06 文件新增「触发场景处理」章节**(输入/产出之后、方式之前):三问判定链(①有无设定→无则功能三或全链路 ②卡片是否已存在→有则询问覆盖/微调 ③模块是否完整→缺则裁剪适配)+ 7 情况表(有设定无卡/有设定有卡/无设定在库/无设定不在库/设定缺模块/参考样式/调整已有卡)+ 环境路径 +- **SKILL.md 八功能六**补「触发判定」行(指向 06 触发场景处理) +- 源技能 → dsh 副本同步,06 文件与 SKILL.md diff 全一致 + +## 功能六「内容基因 TOP6 全量」术语澄清(11:25-11:28) +- 用户问「账号设定中 TOP6 全量验证 是想说根据TOP6视频分析得出的吗」→ 确认混淆点:模块映射表第 4 行原写「内容基因(TOP6 全量)」,易误读为「TOP6 个基因」 +- **改 06 文件**模块映射表第 4 行:「内容基因(TOP6 全量)」→「内容基因(设定中全部条目,全量呈现)」+ 新增术语澄清块(第 77 行):本表「内容基因全量」= 设定文件中内容基因条目**全部上卡、不裁剪**(旧梦留声机恰 6 条,非指 TOP6 视频);内容基因条目由**功能三基于 TOP6 视频**(筛选权威参数见 SKILL.md「视频筛选标准」)解析提炼得出——TOP6 视频是分析依据(输入),内容基因是结论呈现(输出),两个「6」层级不同,勿混淆 +- 源技能 → dsh 副本同步,grep 确认两处第 77 行内容一致,且全文仅此一处含 TOP6 字样 + +## 功能六「六大内容基因·TOP6」表述简化(11:29-11:31) +- 用户反馈:「简化为 六大内容基因·TOP6 就行了 后续生成卡片也这样描述就行」 +- **改 06 文件**:模块映射表第 4 行「内容基因(设定中全部条目,全量呈现)」→「内容基因(功能三基于 TOP6 视频提炼的结论)→ 六大内容基因·TOP6」;术语澄清块精简为一行注(保留核心:TOP6 视频=分析依据输入、内容基因=结论输出、卡片全量呈现不裁剪)+ 明确「后续生成卡片统一以『六大内容基因·TOP6』描述该模块」 +- 源技能 → dsh 副本 cp 同步,diff 一致 + +## 功能六「内容基因·TOP6」定名 + 旧梦产出同步修改(11:31-11:33) +- 用户两连纠正:「简化为 六大内容基因·TOP6」→ 随即「应该改为 内容基因·TOP6」(去掉「六大」) +- **06 文件**(源+dsh):模块映射表第 4 行信息图模块「六大内容基因·TOP6」→「**内容基因·TOP6**」;术语注末句同步改「后续生成卡片统一以『内容基因·TOP6』描述该模块」 +- **旧梦留声机产出同步**(桌面 MCNSkill项目/旧梦留声机/): + - `旧梦留声机账号设定.md`:小标题「六个内容基因标记(TOP6 全量验证)」→「**内容基因·TOP6**」;九差异化壁垒表「六个内容基因是频道级复用资产」→「内容基因·TOP6 是频道级复用资产」 + - `旧梦留声机账号设定卡.svg`:模块标题「▍六大内容基因(TOP6 全量验证)」→「▍**内容基因·TOP6**」;desc 与代码注释同步改 +- grep 验证三目录(源/dsh/桌面)无「六大内容基因/六个内容基因/TOP6 全量」残留 + +## 「核心气质」→「内容标签」改名(11:32-11:34) +- 用户反馈:「核心气质 应该 改为 内容标签」 +- **06 文件**(源+dsh):模块映射表第 3 行「核心气质/人设标签 | 核心气质(chips)」→「内容标签/核心标签 | 内容标签(chips)」 +- **模板样例 SVG**(源+dsh):desc/注释/模块标题 3 处「核心气质」→「内容标签」;顺带补上一轮遗漏——desc 里「六大内容基因」→「内容基因·TOP6」、注释+模块标题「▍六大内容基因(TOP6 全量验证)」→「▍内容基因·TOP6」 +- **06 文件裁剪适配表**(源+dsh):「保留『六大内容基因』」→「保留『内容基因·TOP6』」(上一轮漏改) +- **桌面产出**(旧梦留声机):卡片 SVG 3 处「核心气质」→「内容标签」(sed) +- 验证:源/dsh 两文件 diff 全一致;三目录 grep「核心气质|六大内容基因|六个内容基因」零残留 + +## 「内容路线」→「叙事结构」全链路改名(11:34-11:37) +- 用户反馈:「内容路线 改为 叙事结构」——本次术语同时是设定字段名(设定 md 第 16 行)与卡片模块名,按命名一致性全链路统一 +- **源技能 sed 批量替换 4 文件**(06 文件 4 处:模块映射表第 2 行/触发场景第 5 行/裁剪适配 96/97 行;人设卡生成方法.md 3 处:模板维度定义+校验清单 2 处;模板样例 SVG 3 处:desc/注释/标题;SKILL.md 1 处:功能六「无流程删叙事结构」)→ cp 同步 dsh 副本,diff 4 文件全一致 +- **桌面产出**:设定 md 第 16 行字段「内容路线」→「叙事结构」;卡片 SVG desc/注释/标题 3 处同步 +- **不动**:`.bak` 备份、`视频分析/*/analysis.json` 历史解析原始数据(保持数据准确性);dsh tmp/backfill_track_ztq.py 历史回填脚本(其他账号数据文本) +- 验证:功能文档三处 grep「内容路线」零残留 + +## 生成王微斯账号设定卡(11:45-11:52,功能六首个新账号实例) +- 用户需求:「生成 王微斯的 账号设定卡」;触发场景=**有设定无卡→全流程生成**(桌面 MCNSkill项目/王微斯/王微斯账号设定.md,08-13) +- **产出** `王微斯账号设定卡.svg`(680×1716,深色长图,同模板版式骨架) +- **配色适配**(不沿用模板深棕红):甜宠情感/乙女向→风格映射「甜美少女→深莓紫/浅粉白」→ 背景 #2A0F2E→#18071C 渐变、卡片 #FDF0F6 浅粉白+边框 #F0C9DD、强调玫红 #E0437F/#D84A8C、chips 深莓 #3D1030 +- **模块裁剪适配**(王微斯 vs 旧梦差异):①内容基因 **4 条**→2×2 卡(卡宽 285=582-12/2,非模板 3 列)②情绪 **四段式**→4 条形(开场76/推拉100/破防128玫红高亮/收口88,底边全对齐 1452)③角色类型库=**女主横卡(582×100)+男主 4 卡 2×2**(无英文代号省略斜体行)④叙事结构 5 步→5 卡 104 宽⑤**删色彩体系模块**(设定无色彩体系段,不硬塞)⑥内容标签 12 chips 两行 +- **验证**:svg/g 配对(1/1,15/15)、4 条形底边 1452 全对齐、模块间距≥30px、最长文字行(红线19字)不超卡右缘 ✓ + +## 配色适配·底色=背景色 修正(11:49-11:53) +- 用户澄清:「我说的是改底色就是背景色,不是改里面的元素的颜色」——之前我把映射表写成「背景色/卡片色/强调色」3 列并改了王微斯卡片元素色,理解偏了 +- **修正 06 文档配色适配章节**(源+dsh):①明确「底色=整卡背景色」,卡片/文字/标签/强调色等内部元素颜色**不随账号变**,统一按下方「配色规范」表(浅米卡+深色字+珊瑚红强调);②映射表去「卡片色」「强调色」列,只留「背景底色」;③强规则加「**只换背景色**:内部元素一律按配色规范表」;④配色规范表标题「旧梦留声机定版」→「模板统一色,所有账号通用;仅背景色随账号风格换」 +- **改 SKILL.md**(源+dsh)功能六配色要点:明确「底色=整卡背景色,只换背景色,内部元素保持模板统一色」 +- **修正王微斯卡元素色**(sed 批量):卡片 #FDF0F6→#FAEEDA、边框 #F0C9DD→#EDCBA4、标题 #7B2D5E→#7B2D1B、正文 #6F4A5E→#6F5B4A、GENE #D84A8C→#C13B2F、chips #3D1030→#3D1810+边框 #A8879B→#A88770、标题渐变 #C0457F→#8C2F1E / #E876A9→#C05A32、副题 #8C3B6B→#8C3B2B、情绪条 #F7D9E8→#F7D9A8 / #F2C0DA→#F0B25C / #E0437F→#D6403A / #C22F6A→#A92825、红线 #E0437F→#D6403A、版权 #8A5E7A→#7A5645;**仅保留**背景渐变 #241020→#12070F(深莓紫,甜美/甜宠风格映射) +- 验证:svg/g 1/1+15/15、模板色使用 96 处、粉色系残留 0;CDP 截图渲染确认背景深莓紫+元素全模板色(系列感一致) +- 脚本保留 cdp-shot-wangweisi.mjs(本地 SVG 截图自检) + +## 深莓紫背景调亮(11:52-11:54) +- 用户反馈:「背景色 我看还是黑色呢」——原映射色 #241020→#12070F 压太暗,紫调不可辨,视觉近黑 +- **调亮**:王微斯卡 bgGrad `#241020→#12070F` → `#3A1E42→#22102A`(顶部 R58/G30/B66 紫调明显、亮度≈旧梦深棕红 2 倍;底部深紫 #22102A,仍保深色底) +- **06 文档映射表**(源+dsh)甜美/少女/二次元行同步更新色值 +- 经验固化:深色渐变底顶部色需保紫/蓝/绿等色相可辨(亮度≈50/255),过暗则视觉归黑;旧梦深棕红 #2B0F0A 亮度 23 但因红棕调暖尚可辨,冷色调更易显黑需提亮 + +## 工作台账号设定内容区左右 +20px 间隔(11:42-11:44) +- 用户反馈:「账号设定卡 左右可以在间隔20PX」→ 澄清:不是改 SVG,是**工作台账号详情「账号设定」tab 内容区距页面左右再增 20px 间隔** +- **改 style.css** `.persona-wrap`: `position: relative` → `position: relative; padding: 0 20px;`(卡片版 .card-view 与文字版 .persona-text 同在 persona-wrap 内,左右各内缩 20px;绝对定位的 .persona-vs-icons 切换图标不受 padding 影响仍贴右上角) +- **验证**(CDP #/account/1629 强制刷新 + 点 persona tab):computed padding-left/right=20px;卡片 imgW 1122.7 vs detail-body 1172.7,内缩合计 50px(左 detail-body 10 + persona 20,右 persona 20);文字版 inset 同 50 ✓ +- 静态 CSS 改动无需重启服务(HTTP 200);验证脚本保留 `cdp-verify-personapad.mjs` + +## 王微斯账号设定卡重写为浅色甜宠风(11:54-11:55) +- 用户反馈:「这个账号的调性 应该是没那么深的颜色」——深莓紫 #3A1E42→#22102A 仍偏暗,账号整体偏甜宠少女应走明快调性 +- **重写** `桌面\MCNSkill项目\王微斯\王微斯账号设定卡.svg` 为浅色甜宠风(布局坐标 680×1716 不变,全量换色): + - 背景:`#3A1E42→#22102A` 深莓紫 → **`#F8EDF6→#F1E0EE` 浅粉紫渐变** + - 顶部标题框:白 `#FFFFFF` + 粉边 `#E8C9DC`,主标题渐变 `#C0457F→#E876A9`(粉),副标题/标语/模块标题 `#7B2D5E`(深粉) + - 五步推拉 / 内容标签 / 基因卡 / 角色库:内部元素全部从「模板统一色 + 粉调」改回「白卡 + 粉边 + 粉 chips」体系(chips `#F7E8F2`/`#D8A9C8`、GENE 标签 `#E0437F`、正文 `#6F4A5E`) + - 四段式情绪:浅粉/中粉/破防段 `#E0437F` 高亮(白字 700)/浅粉,注释 `#B98AA6`、小字 `#A98A9C` + - 红线清单:浅粉条 `#F7E8F2` + 粉×号 `#E0437F`、文字深粉 +- **06 文档新增「浅色系配色规范」**(源+dsh 同步): + - 配色适配章节重写:底色**分两系**(深色系=深沉浓郁调性,浅色系=明快/甜美/治愈调性),内部元素跟随所选色系,不再「只换背景色」→ 改为「**底色决定色系**」 + - 关键定义:浅色系 = 白卡 + 粉边 + 深粉文字 + 粉强调 + - 风格映射表「甜美/少女/二次元」行:深莓紫(调性深沉时)|**浅色甜宠风 `#F8EDF6→#F1E0EE`(调性明快时,如王微斯)** + - 新增浅色系推导公式:主色 → 提亮 90%+ 亮度 + 大幅降饱和 → 浅色渐变底;正文用主色加深版(`#7B2D5E` 档) + - 配色规范表拆为两套并列:「**深色系(深色长图风,如旧梦留声机)**」+「**浅色系(浅色甜宠风,如王微斯)**」(含背景/标题框/主标题渐变/副标题/模块标题/卡片背景×2类/卡片标题/正文/GENE/英文代号/情绪图破防/琥珀段/小字/红线×号 14 行色值) + - 强规则第 2 条「只换背景色」→「底色决定色系」 +- **验证**:源/dsh 06 文档 diff 全一致;CDP 截图(`shot-wangweisi-card.png`)渲染确认浅粉紫底+白卡+深粉字+破防段粉红高亮,整体调性明快甜宠 ✓ +- 经验固化:**配色分两系而非单系**——深色系适合剧情/怀旧/深沉调性,浅色系适合甜宠/少女/治愈/明快调性;同一账号必须固定色系,跨账号可不同;未来生成新账号卡片时按风格落系,色值从对应色系表查,禁跨系混用 +- 脚本升级:`cdp-shot-wangweisi.mjs` 页面匹配 `decodeURIComponent(t.url).includes('王微斯')`(9223 URL 百分号编码坑)+ 补 `Page.reload({ignoreCache:true})`(同 URL navigate 不重载) + +## 王微斯卡片·玫瑰金背景试版(12:01-12:03) +- 用户需求:「这个保留 再试试 偏粉的深一点的玫瑰金色」——保留当前浅色甜宠风版式与内部元素,仅换**背景**为偏粉深玫瑰金渐变 +- **改** `桌面\MCNSkill项目\王微斯\王微斯账号设定卡.svg`: + - 渐变:`#F8EDF6→#F1E0EE` 浅粉紫 → **`#D6A3A9→#9A5563`**(顶部偏粉玫瑰金 → 底部深玫瑰金) + - 背景上直接书写的元素提亮(背景变深需保可读):6 处模块标题 `#7B2D5E`→`#FDE8EC`(浅粉白)、装饰线+4 处情绪百分比小字 `#B98AA6`→`#EAC6CE`、斜体行+版权 2 行 `#A98A9C`→`#DDBCC4` + - **不动**:白卡 #FFFFFF×15、粉 chips #F7E8F2×20、卡片内深粉文字/粉强调等全部内部元素(遵守「改底色=改背景色」原则) +- **备份**:原浅粉紫版 → `王微斯账号设定卡_浅粉紫底.bak.svg`(同目录,可回退) +- **验证**(CDP 断言+截图 shot-wangweisi-rosegold.png):bg0=#D6A3A9 / bg1=#9A5563 / 6 标题 #FDE8EC / 装饰线 #EAC6CE / 版权 #DDBCC4 / 白卡 15 / 粉 chips 20 全保留 ✓ +- 验证脚本保留 `cdp-verify-rosegold.mjs`(断言渐变 stop + 背景上文字 + 内部元素计数,可复用) + +## 王微斯卡片·定稿玫瑰金底(12:03-12:05) +- 用户反馈:「感觉那个(玫瑰金)更适合 这个账号调性」→ 玫瑰金底定稿,当前 `王微斯账号设定卡.svg`(#D6A3A9→#9A5563)为最终版;浅粉紫备份 `_浅粉紫底.bak.svg` 保留可回退 +- **06 文档固化**(源+dsh 同步,diff 一致): + - 映射表甜美行:深莓紫(深沉时)|**玫瑰金 #D6A3A9→#9A5563(明快时,如王微斯·定稿)** + - 浅色系推导改「提亮+降饱和至中浅明度(约 65-90% 亮度)」+ 新增「背景上直接书写的文字随背景深浅提亮保可读」(玫瑰金底:模块标题 #FDE8EC/装饰线注释 #EAC6CE/斜体版权 #DDBCC4) + - 浅色系表更新:背景=玫瑰金(更浅可回退浅粉紫)、模块标题=#FDE8EC、情绪小字=#EAC6CE;表后加注释(背景越浅越近深粉字、越深越近浅粉白) +- **SKILL.md 功能六配色段更新**(源+dsh diff 一致):「只换背景色」→「底色决定色系」两系完整表述 + 玫瑰金映射 + 浅色系推导 + 背景文字提亮 +- **MEMORY.md**:浅色系定稿色值更新为玫瑰金 +- 经验固化:用户试色闭环=「先试浅→再试深一点有色相→选定稿」,配色迭代时**备份每个候选版**(.bak.svg)防回退;色系表以账号定稿为准反哺文档,避免文档停留在试色中间态 + +## 王微斯卡片·叙事结构箭头位置修复(12:07-12:08) +- 用户反馈:「叙事结构 卡片之间的箭头 都偏到 卡片里面了」 +- **根因**:卡片缝隙仅 15px,但箭头 line 只画了 2px(如 x1=153 x2=155),marker 宽 6px(refX=8 → 尾部在 x2-4.8),箭头主体伸进左侧卡片约 3px +- **修复**:4 条箭头线拉长占满缝隙——154→166 / 273→285 / 392→404 / 511→523(各 12px;卡右缘+1 起、卡左缘-2 止),箭头整体居中于两卡之间,尾部不碰卡1、尖部离卡2 左缘 2px +- **验证**(CDP 断言):4 线 x1/x2 全对、线长≥11px(arrowsInGap=true)、背景/标题/白卡15/粉chips20 不受影响 ✓ +- 经验固化:**卡片间箭头/连线必须按缝隙宽度画线**(缝隙宽-3~4px 的线长),marker 宽度决定最小线长(线长须 ≥ marker 渲染宽 + 1px,否则箭头伸进相邻元素);同款五步卡布局(104 宽×5 + 15px 缝隙)复用时直接套用此坐标 + +## 固化:功能三自动联动生成设定卡 + 双向联动维护(12:12-12:15) +- 用户需求:「给用户获取账号设定时 自动生成设定卡片,固化到skill中,修改时两边一起修改」——①功能三产出设定后**自动**生成卡片(无需用户开口)②设定 md 与卡片 SVG **双向联动**同步修改,全部固化到技能 +- **SKILL.md**(源+dsh):功能总览表功能六「功能三之后可选」→「**功能三自动联动(默认)**」;部署禁用说明补「删除功能六文件则不再自动生成(存量可手动)」;功能三核心流程加 **Step 7(自动联动)**;功能六章节加「自动触发(默认)」+「双向联动维护」 +- **03_提炼账号设定.md**(源+dsh):新增 **Step 7:自动生成账号设定卡片(联动)**——默认自动触发(跳过条件:功能六文件删除/用户明确只要设定)+ 双向联动维护 3 条规则(改设定→卡片同步、改卡片→设定同步、术语改名→两边一起改)+ grep 一致性校验 +- **06_生成账号设定卡片.md**(源+dsh):使用场景改「默认由功能三自动联动触发」;触发场景处理加「默认自动触发」说明;情况 1 注「自动流程已覆盖」;环境路径后新增「**双向联动维护**」章节(含历史改名案例:内容路线→叙事结构、核心气质→内容标签、六大内容基因→内容基因·TOP6) +- 验证:3 文件源=dsh diff 全一致;MEMORY.md 联动规则已固化 +- 经验固化:功能联动类需求固化落点=SKILL.md(总览/章节)+ 上游功能文档(自动触发的源头)+ 下游功能文档(触发判定与维护规则);「修改时两边一起修改」的准确载体=设定 md ↔ 卡片 SVG 两个产出文件(非源技能/dsh,那是既有同步策略) + +## mcn-video-prompt 提示词输出结构 4层→6层重排(用户定稿,14:29) +- **新结构**:①主体列表 → ②画面质感(视觉风格+摄影质感参数,视觉基调前置)→ ③色调光影(光源+主光方向+亮暗区+色调/色温)→ ④主体设定(人物/场景/构图)→ ⑤镜头叙事 → ⑥负面提示词;原「②设定」层拆为画面质感/色调光影/主体设定三层,视觉基调前置(模型对提示词开头权重最高) +- **修改文件 3 个**(逐条改+验证): + - `subskill/mcn-video-prompt/references/制作流程/分镜提示词制作流程.md`(权威出处,9 处):3.2/3.2.2 标题、改造原则、3.2.1 思考清单层号映射、3.2.2 输出格式块整体重排、6层×5类型适配表(列扩为⑦列)、Step5 校验②结构完整性、总流程 3 处引用、对比表技术参数行 + - `subskill/mcn-video-prompt/SKILL.md`(8 处):F4 提示词内部结构、F2/F4 详细方法论引用×2、剧本分析前置段、工作流 Step3、提示词模板节×2、自检⑧结构完整性;frontmatter updated_at=2026-08-31+last_change + - `subskill/mcn-video-prompt/references/制作流程/长视频分段制作流程.md`(4 处):一致性锁定表(②设定-视觉风格→②画面质感等)、简单段/复杂段输出模板重排为 6 层 +- 验证:全局 grep 旧层号零残留("4层"仅剩 last_change 变更描述+L189 F2"4层退化策略"无关概念);6层落位 SKILL.md×8/分镜提示词制作流程×7/长视频分段×3 +- 注:dsh 副本(storyboard-prompt)未同步,按 .dsh 单向只读约定待用户授权 diff --git a/.workbuddy/memory/MEMORY.md b/.workbuddy/memory/MEMORY.md index 7b50192..4bc4767 100644 --- a/.workbuddy/memory/MEMORY.md +++ b/.workbuddy/memory/MEMORY.md @@ -10,8 +10,8 @@ ## 核心技能 - **短视频脚本创作** `project/短视频脚本创作/{V1.0,Lite1.0}/`:11步流程+知识库12分类(66文件);V1.0 含 MCP/Dify 接口,Lite1.0 去全部接口、模型自带 WebSearch。**同步策略(08-26 用户定)**:只改 V1.0 源技能+dsh 副本两处;Lite1.0 默认不动 -- **MCN工作台(08-27,仅 V1.0)** `project/短视频脚本创作/V1.0/mcn-work-shop/`(server.js+public/,零依赖 Node,端口8899自动避让);触发「打开工作台」→ 启动服务+内置浏览器打开。hash 路由:首页+📈账号周榜/👥热门账号/账号详情/视频详情/AI写脚本/AI写作复盘(读自有 mcn-plugin.db 副本+dshworkspace 榜单 JSON)+📁产出浏览(桌面 MCNSkill项目/ 账号层直挂)+❓帮助页。**AI 任务触发(08-27 定版;归 mcn-workshop 空间)**:数据页按钮→`confirmRun()` 确认弹窗→`executeTask()` `POST /api/run` 写 `~/.workbuddy/workbuddy.db` automations(once+now,cwds=`D:\AgentSkill\mcn-workshop`)→WorkBuddy 自动执行创建会话→左侧会话栏 mcn-workshop 空间可见;轮询 `GET /api/run/status?id=`(读 automations/automation_runs/automation_runtime_state:running=IN_PROGRESS/running、done=DONE/COMPLETED/SUCCESS、error=ERROR/CANCELLED;5s 间隔 30min 上限)→done/error 恢复按钮(.btn-busy「执行中…」);**用户明确不同步 dsh/Lite**(刻意 divergence);AI脚本选题(08-28):关注账号「创作」→「AI脚本」`confirmAiScript` 选题 textarea→`POST /api/ai/topics`(查库带账号摘要→S5 方法论 prompt→Dify chat-messages recall 即时返回约50s);Dify key 内置 `scripts/MCN_CYLG_API.py` 的 `DEFAULT_CYLG_KEY`(env `DIFY_MCN_CYLG_KEY` 优先;key=app-0vRaxPhvVOqzFQIa5eFzFdjD),`DIFY_URL` 默认 mydify.youmanvideo.com/v1、model 默认 gemini、curl 中文被 CDN 拦推荐 Python、search 易超时用 recall -- **抖音账号分析 mcn-dou-analysis(主版本 08-27)**:源=`project/短视频脚本创作/V1.0/subskill/mcn-dou-analysis/`;dsh 副本=`C:\Users\maidou\.dsh\skills\mcn-dou-analysis\`;旧路径 `project/短视频账号分析/case_1/` 已废弃。五大功能:F1账号信息/F2视频列表(13列Excel)/F3提炼账号设定(人设卡12段)/F4分析视频数据(逐条→汇总两段式)/F5解析视频(单视频MCP)。红线:达人账号表15列(仅用户要求增改)、TOP6筛选(点赞+分享降序,权威参数在SKILL.md)、≥60条停止滚动、29赛道枚举、源技能只读单向同步、技能边界(脚本必须切脚本创作技能)、浏览器9223调试实例+标签页复用 +- **MCN工作台(08-27,仅 V1.0)** `project/短视频脚本创作/V1.0/mcn-work-shop/`(server.js+public/,零依赖 Node,端口8899自动避让);触发「打开工作台」→ 启动服务+内置浏览器打开。hash 路由:首页+📈账号周榜/👥热门账号/账号详情/视频详情/AI写脚本/AI写作复盘(读自有 mcn-plugin.db 副本+dshworkspace 榜单 JSON)+📁产出浏览(桌面 MCNSkill项目/ 账号层直挂)+❓帮助页。**首页顶部数据预览条(08-31,参考 dsh-plugin-mcn 首页 stats 移植)**:`/api/dsh/stats` 5 项=热门账号(accounts)/视频解析(analyses)/AI写脚本(rewrites)/AI写作复盘(reviews type='ai')/AI写作评分(writingScore=最新20条AI复盘平均分),条带=7%品牌蓝底+数值26px粗体+右分隔线(样式在 style.css .home-stats);**AI 任务触发(08-27 定版;归 mcn-workshop 空间)**:数据页按钮→`confirmRun()` 确认弹窗→`executeTask()` `POST /api/run` 写 `~/.workbuddy/workbuddy.db` automations(once+now,cwds=`D:\AgentSkill\mcn-workshop`)→WorkBuddy 自动执行创建会话→左侧会话栏 mcn-workshop 空间可见;轮询 `GET /api/run/status?id=`(读 automations/automation_runs/automation_runtime_state:running=IN_PROGRESS/running、done=DONE/COMPLETED/SUCCESS、error=ERROR/CANCELLED;5s 间隔 30min 上限)→done/error 恢复按钮(.btn-busy「执行中…」);**用户明确不同步 dsh/Lite**(刻意 divergence);AI脚本选题(08-28):关注账号「创作」→「AI脚本」`confirmAiScript` 选题 textarea→`POST /api/ai/topics`(查库带账号摘要→S5 方法论 prompt→Dify chat-messages recall 即时返回约50s);Dify key 内置 `scripts/MCN_CYLG_API.py` 的 `DEFAULT_CYLG_KEY`(env `DIFY_MCN_CYLG_KEY` 优先;key=app-0vRaxPhvVOqzFQIa5eFzFdjD),`DIFY_URL` 默认 mydify.youmanvideo.com/v1、model 默认 gemini、curl 中文被 CDN 拦推荐 Python、search 易超时用 recall +- **抖音账号分析 mcn-dou-analysis(主版本 08-27)**:源=`project/短视频脚本创作/V1.0/subskill/mcn-dou-analysis/`;dsh 副本=`C:\Users\maidou\.dsh\skills\mcn-dou-analysis\`;旧路径 `project/短视频账号分析/case_1/` 已废弃。**六大功能(08-31 加 F6)**:F1账号信息/F2视频列表(13列Excel)/F3提炼账号设定(人设卡12段)/F4分析视频数据(逐条→汇总两段式)/F5解析视频(单视频MCP)/**F6生成账号设定卡片**(SVG 长图 680×~1716,模板=旧梦留声机样例)。红线:达人账号表15列(仅用户要求增改)、TOP6筛选(点赞+分享降序,权威参数在SKILL.md)、≥60条停止滚动、29赛道枚举、源技能只读单向同步、技能边界(脚本必须切脚本创作技能)、浏览器9223调试实例+标签页复用。**F6 卡片配色分两系(08-31 定型)**:**深色系(深色长图风,深沉/浓郁调性,如旧梦留声机)**=深棕红渐变底 #2B0F0A→#1A0805 + 浅米卡 #FAEEDA + 深色字 + 珊瑚红强调;**浅色系(浅色甜宠风,明快/甜美/治愈调性,如王微斯·定稿玫瑰金)**=玫瑰金渐变底 #D6A3A9→#9A5563 + 白卡 #FFFFFF + 粉边 #E8C9DC + 深粉字 #7B2D5E + 粉强调 #C0457F→#E876A9;背景上直接书写的文字随背景深浅提亮(玫瑰金底:模块标题 #FDE8EC/注释 #EAC6CE/版权 #DDBCC4);更浅可回退浅粉紫 #F8EDF6→#F1E0EE;底色决定色系,**同一账号必须固定色系**,跨账号可不同;禁跨系混用。模块名统一为「叙事结构」「内容标签」「**内容基因·TOP6**」。F6 触发场景处理(07情况表+三问判定链)在 06 文档。**功能三→功能六自动联动(08-31 固化)**:功能三产出 `{账号名}账号设定.md` 后**自动生成设定卡**(无需用户开口,删除 06 文件即禁用自动);**设定 md ↔ 卡片 SVG 双向联动维护**——改设定→卡片同步(内容级重生成/单点改 SVG)、改卡片→设定同步、术语改名→两边一起改(历史案例:内容路线→叙事结构、核心气质→内容标签),交付前 grep 校验关键术语两文件一致;规则固化于 SKILL.md(功能三 Step7/功能六自动触发)+ 03 文档 Step7 + 06 文档触发场景/双向联动维护 - **短视频分镜脚本** `project/短视频提示词生成/`(08-27 去 V1.0 层):4功能 F1反推图片/F2反推视频/F3生成图片/F4生成分镜;三层引用架构 参考skills→references/→references-add/(运行时优先;开发机不启用 references-add/,dsh/用户环境启用);知识库15维度(01-12用户+13音效+14构图+15风格);F4输出=主题+导演阐述+代码块(>5s时间戳分段/≤5s单段);提示词模板类型分档(A剧情/B单人Vlog/C旁白知识/D产品展示/E纯氛围)+4层×5类型适配表;公式只留时间预算表等内部环节,提示词正文只写时长结论;seedance25 时间测算体系(语速6公式+动作14+运镜7+帧级换算+超调6级);dsh 副本无独有内容需回流(唯一独有项 `name: storyboard-prompt` 环境保留) ## 创作技能关键规则(权威源:SKILL.md 关键规则 + references/创作流程规范.md) diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-shot-wangweisi.mjs b/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-shot-wangweisi.mjs new file mode 100644 index 0000000..b62b00c --- /dev/null +++ b/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-shot-wangweisi.mjs @@ -0,0 +1,24 @@ +// 截图王微斯账号设定卡 SVG 渲染效果(自检配色) +const list = await fetch('http://localhost:9223/json').then((r) => r.json()); +let page = list.find((t) => t.type === 'page' && (decodeURIComponent(t.url).includes('王微斯') || t.url.includes('8899'))); +if (!page) { console.log('ERR: no wangweisi/8899 page open'); process.exit(1); } +const ws = new WebSocket(page.webSocketDebuggerUrl); +let id = 0; const pending = new Map(); +const send = (method, params = {}) => new Promise((res, rej) => { const i = ++id; pending.set(i, { res, rej }); ws.send(JSON.stringify({ id: i, method, params })); }); +ws.onmessage = (e) => { const m = JSON.parse(e.data); if (m.id && pending.has(m.id)) { const p = pending.get(m.id); pending.delete(m.id); m.error ? p.rej(new Error(m.error.message)) : p.res(m.result); } }; +await new Promise((r) => (ws.onopen = r)); +const ev = async (expr) => { const r = await send('Runtime.evaluate', { expression: expr, returnByValue: true, awaitPromise: true }); if (r.exceptionDetails) return { __err: (r.exceptionDetails.exception?.description || r.exceptionDetails.text).slice(0, 300) }; return r.result.value; }; +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); +await send('Page.enable'); await send('Runtime.enable'); + +// 导航到本地 SVG 文件并强制重载(同一 URL 导航不触发重载) +const url = 'file:///C:/Users/maidou/Desktop/MCNSkill项目/王微斯/王微斯账号设定卡.svg'; +await send('Page.navigate', { url }); +await sleep(1200); +await send('Page.reload', { ignoreCache: true }); +await sleep(2500); +const r = await send('Page.captureScreenshot', { format: 'png' }); +const fs = await import('node:fs'); +fs.writeFileSync('shot-wangweisi-card.png', Buffer.from(r.data, 'base64')); +console.log('截图完成: shot-wangweisi-card.png'); +ws.close(); diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-btnwhite.mjs b/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-btnwhite.mjs new file mode 100644 index 0000000..ee228ef --- /dev/null +++ b/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-btnwhite.mjs @@ -0,0 +1,36 @@ +// 验证:周榜功能列按钮统一「白底带边框」(btn-view / btn-import) +const list = await fetch('http://localhost:9223/json').then((r) => r.json()); +let page = list.find((t) => t.type === 'page' && t.url.includes('localhost:8899')); +if (!page) { console.log('ERR: no workbuddy page open'); process.exit(1); } +const ws = new WebSocket(page.webSocketDebuggerUrl); +let id = 0; const pending = new Map(); +const send = (method, params = {}) => new Promise((res, rej) => { const i = ++id; pending.set(i, { res, rej }); ws.send(JSON.stringify({ id: i, method, params })); }); +ws.onmessage = (e) => { const m = JSON.parse(e.data); if (m.id && pending.has(m.id)) { const p = pending.get(m.id); pending.delete(m.id); m.error ? p.rej(new Error(m.error.message)) : p.res(m.result); } }; +await new Promise((r) => (ws.onopen = r)); +const ev = async (expr) => { const r = await send('Runtime.evaluate', { expression: expr, returnByValue: true, awaitPromise: true }); if (r.exceptionDetails) return { __err: (r.exceptionDetails.exception?.description || r.exceptionDetails.text).slice(0, 200) }; return r.result.value; }; +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); +await send('Page.enable'); await send('Runtime.enable'); +const goto = async (hash) => { await send('Page.navigate', { url: 'http://localhost:8899/' + hash }); await sleep(1500); await send('Page.reload', { ignoreCache: true }); await sleep(3000); }; + +// 1) 功能页 #/ranking:查看 + 分析按钮 +await goto('#/ranking'); +const rank = await ev(`(() => { + const btns = [...document.querySelectorAll('.table tbody .cell-act .btn')]; + return btns.slice(0, 4).map((b) => ({ txt: b.textContent.trim(), bg: getComputedStyle(b).backgroundColor, border: getComputedStyle(b).borderTopColor, color: getComputedStyle(b).color })); +})()`); +console.log('【功能页功能列】'); +for (const b of rank) console.log(' ', b.txt, '| bg:', b.bg, '| border:', b.border); + +// 2) 首页预览:查看 + 关注按钮 +await goto('#/'); +const home = await ev(`(() => { + const btns = [...document.querySelectorAll('#rankPreviewList .rank-row:not(.rank-head) .rk-act .btn')]; + return btns.slice(0, 4).map((b) => ({ txt: b.textContent.trim(), bg: getComputedStyle(b).backgroundColor, border: getComputedStyle(b).borderTopColor })); +})()`); +console.log('【首页预览功能列】'); +for (const b of home) console.log(' ', b.txt, '| bg:', b.bg, '| border:', b.border); + +const allBtns = [...rank, ...home]; +const ok = allBtns.length > 0 && allBtns.every((b) => b.bg === 'rgb(255, 255, 255)' && b.border !== 'rgb(245, 246, 248)'); +console.log(ok ? 'PASS: 全部白底带边框' : 'CHECK'); +ws.close(); diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-homestats.mjs b/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-homestats.mjs new file mode 100644 index 0000000..76aeca6 --- /dev/null +++ b/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-homestats.mjs @@ -0,0 +1,52 @@ +// 验证「首页顶部数据预览」条带(参考 dsh-plugin-mcn 首页 stats):只读测量 +const list = await fetch('http://localhost:9223/json').then((r) => r.json()); +let page = list.find((t) => t.type === 'page' && t.url.includes('localhost:8899')); +if (!page) { console.log('ERR: no workbuddy page open'); process.exit(1); } +const ws = new WebSocket(page.webSocketDebuggerUrl); +let id = 0; const pending = new Map(); +const send = (method, params = {}) => new Promise((res, rej) => { const i = ++id; pending.set(i, { res, rej }); ws.send(JSON.stringify({ id: i, method, params })); }); +ws.onmessage = (e) => { const m = JSON.parse(e.data); if (m.id && pending.has(m.id)) { const p = pending.get(m.id); pending.delete(m.id); m.error ? p.rej(new Error(m.error.message)) : p.res(m.result); } }; +await new Promise((r) => (ws.onopen = r)); +const ev = async (expr) => { const r = await send('Runtime.evaluate', { expression: expr, returnByValue: true, awaitPromise: true }); if (r.exceptionDetails) return { __err: (r.exceptionDetails.exception?.description || r.exceptionDetails.text).slice(0, 300) }; return r.result.value; }; +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); +await send('Page.enable'); await send('Runtime.enable'); + +// 1) 导航到首页并强制刷新加载最新代码(纯 hash 变化不触发文档重载,必须 reload) +await send('Page.navigate', { url: 'http://localhost:8899/#/' }); +await sleep(1500); +await send('Page.reload', { ignoreCache: true }); +await sleep(3000); + +// 2) 检查 stats 条带结构 + 渲染值 + 样式 +const check = await ev(`(() => { + const el = document.getElementById('homeStats'); + if (!el) return { err: 'no #homeStats' }; + const items = [...el.querySelectorAll('.home-stat-item')]; + return { + count: items.length, + values: items.map((i) => ({ v: i.querySelector('.home-stat-value').textContent, l: i.querySelector('.home-stat-label').textContent })), + position: el.previousElementSibling ? el.previousElementSibling.className : '(first in .home)', + next: el.nextElementSibling ? el.nextElementSibling.className : '', + }; +})()`); +console.log('条带结构:', JSON.stringify(check, null, 1)); + +// 3) computed 样式核对(条带底色 / 数字色 / item 分隔线) +const styleCheck = await ev(`(() => { + const el = document.getElementById('homeStats'); + if (!el) return { err: 'no el' }; + const cs = getComputedStyle(el); + const v = getComputedStyle(el.querySelector('.home-stat-value')); + const item = el.querySelector('.home-stat-item'); + const its = getComputedStyle(item); + return { + statsBg: cs.backgroundColor, statsRadius: cs.borderRadius, statsPadding: cs.padding, + valueColor: v.color, valueSize: v.fontSize, valueWeight: v.fontWeight, + itemBorderRight: its.borderRightWidth + ' ' + its.borderRightStyle, + itemLast: getComputedStyle(el.querySelector('.home-stat-item:last-child')).borderRightWidth, + }; +})()`); +console.log('样式:', JSON.stringify(styleCheck)); + +ws.close(); +console.log('DONE'); diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-personacard.mjs b/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-personacard.mjs new file mode 100644 index 0000000..5993a8d --- /dev/null +++ b/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-personacard.mjs @@ -0,0 +1,147 @@ +// 验证「账号设定」卡片版/文字版切换(账号详情页 persona tab) +// 用例1:旧梦留声机(1629) 有卡片 → 默认卡片版 + 可切换文字版 +// 用例2:王微斯(1627) 无卡片 → 直接文字版,无切换控件 +const list = await fetch('http://localhost:9223/json').then((r) => r.json()); +let page = list.find((t) => t.type === 'page' && t.url.includes('localhost:8899')); +if (!page) { console.log('ERR: no workbuddy page open'); process.exit(1); } +const ws = new WebSocket(page.webSocketDebuggerUrl); +let id = 0; const pending = new Map(); +const send = (method, params = {}) => new Promise((res, rej) => { const i = ++id; pending.set(i, { res, rej }); ws.send(JSON.stringify({ id: i, method, params })); }); +ws.onmessage = (e) => { const m = JSON.parse(e.data); if (m.id && pending.has(m.id)) { const p = pending.get(m.id); pending.delete(m.id); m.error ? p.rej(new Error(m.error.message)) : p.res(m.result); } }; +await new Promise((r) => (ws.onopen = r)); +const ev = async (expr) => { const r = await send('Runtime.evaluate', { expression: expr, returnByValue: true, awaitPromise: true }); if (r.exceptionDetails) return { __err: (r.exceptionDetails.exception?.description || r.exceptionDetails.text).slice(0, 300) }; return r.result.value; }; +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); +await send('Page.enable'); await send('Runtime.enable'); + +const shot = async (name) => { + try { + const r = await send('Page.captureScreenshot', { format: 'png' }); + const fs = await import('node:fs'); + fs.writeFileSync('shot-' + name + '.png', Buffer.from(r.data, 'base64')); + console.log('截图:', name); + } catch (e) { console.log('截图失败(跳过):', e.message); } +}; + +const goto = async (url) => { + await send('Page.navigate', { url }); + await sleep(2500); + await send('Page.reload', { ignoreCache: true }); // hash 路由 navigate 不重载文档,强制刷新加载最新 JS + await sleep(3000); +}; + +// ---- 用例1:旧梦留声机(有卡片)---- +console.log('=== 用例1 旧梦留声机 1629 ==='); +await goto('http://localhost:8899/#/account/1629'); +// 点击「账号设定」tab +const tabClick = await ev(`(() => { + const b = document.querySelector('[data-tab="persona"]'); + if (!b) return { err: 'no persona tab' }; + b.click(); + return 'clicked persona tab'; +})()`); +console.log('切tab:', JSON.stringify(tabClick)); +await sleep(1800); // 等 loadPersona 两个 fetch 完成 +const s1 = await ev(`(() => { + const sw = document.querySelector('.persona-vs-icons'); + const card = document.querySelector('.card-view'); + const md = document.querySelector('.persona-text'); + const img = card && card.querySelector('img'); + const btns = sw ? [...sw.querySelectorAll('.vi-btn')] : []; + const active = btns.find((x) => x.classList.contains('vi-active')); + const cardIcon = btns[0] && btns[0].querySelector('svg path'); + return { + hasIcons: !!sw, + btnTitles: btns.map((x) => x.title), + activeTitle: active ? active.title : null, + cardIconIsImage: cardIcon ? cardIcon.getAttribute('d').includes('m21 15-3.086') : null, + cardHidden: card ? card.classList.contains('vs-hidden') : null, + cardImgSrc: img ? img.getAttribute('src').slice(0, 60) : null, + mdHidden: md ? md.classList.contains('vs-hidden') : null, + mdLen: md ? md.textContent.length : 0, + }; +})()`); +console.log('默认卡片版状态:', JSON.stringify(s1)); + +// 切到文字版 +await ev(`(() => { const b = document.querySelector('[data-vs="text"]'); if (b) b.click(); return 1; })()`); +await sleep(400); +const s2 = await ev(`(() => { + const sw = document.querySelector('.persona-vs-icons'); + const card = document.querySelector('.card-view'); + const md = document.querySelector('.persona-text'); + const active = sw && [...sw.querySelectorAll('.vi-btn')].find((x) => x.classList.contains('vi-active')); + return { + activeTitle: active ? active.title : null, + activeCount: sw ? [...sw.querySelectorAll('.vi-btn.vi-active')].length : null, + cardHidden: card ? card.classList.contains('vs-hidden') : null, + mdHidden: md ? md.classList.contains('vs-hidden') : null, + }; +})()`); +console.log('切文字版后:', JSON.stringify(s2)); + +// 切回卡片版 +await ev(`(() => { const b = document.querySelector('[data-vs="card"]'); if (b) b.click(); return 1; })()`); +await sleep(400); +const s3 = await ev(`(() => { + const card = document.querySelector('.card-view'); + const md = document.querySelector('.persona-text'); + const active = document.querySelector('.vi-btn.vi-active'); + return { + activeTitle: active ? active.title : null, + activeCount: document.querySelectorAll('.vi-btn.vi-active').length, + cardHidden: card ? card.classList.contains('vs-hidden') : null, + mdHidden: md ? md.classList.contains('vs-hidden') : null, + }; +})()`); +console.log('切回卡片版后:', JSON.stringify(s3)); + +// ---- 用例2:王微斯(无卡片)---- +console.log('=== 用例2 王微斯 1627(无卡片)==='); +await goto('http://localhost:8899/#/account/1627'); +await ev(`(() => { const b = document.querySelector('[data-tab="persona"]'); if (b) b.click(); return 1; })()`); +await sleep(1800); +const s4 = await ev(`(() => { + const sw = document.querySelector('.persona-vs-icons'); + const md = document.querySelector('.persona-text'); + return { + hasIcons: !!sw, + mdExists: !!md, + mdHidden: md ? md.classList.contains('vs-hidden') : null, + mdLen: md ? md.textContent.length : 0, + }; +})()`); +console.log('无卡片账号状态:', JSON.stringify(s4)); + +// 回到用例1 页面截图卡片版效果(最后统一截图,失败不中断) +await goto('http://localhost:8899/#/account/1629'); +await ev(`(() => { const b = document.querySelector('[data-tab="persona"]'); if (b) b.click(); return 1; })()`); +await sleep(1800); + +// 多视口宽度自检:验证图片是否随内容区自适应(不应超出 viewport 引发横向滚动) +for (const vw of [1280, 1024, 900, 1920, 2560]) { + await send('Emulation.setDeviceMetricsOverride', { width: vw, height: 800, deviceScaleFactor: 1, mobile: false }); + await sleep(500); + const info = await ev(`(() => { + const img = document.querySelector('.card-view img'); + const body = document.querySelector('.detail-body'); + const wrap = document.querySelector('.persona-wrap'); + const page = document.querySelector('.page-detail'); + return { + viewport: ${vw}, + docScrollW: document.documentElement.scrollWidth, + bodyW: body ? Math.round(body.getBoundingClientRect().width) : null, + wrapW: wrap ? Math.round(wrap.getBoundingClientRect().width) : null, + imgW: img ? Math.round(img.getBoundingClientRect().width) : null, + pageW: page ? Math.round(page.getBoundingClientRect().width) : null, + bodyOverflow: body ? getComputedStyle(body).overflowX : null, + }; + })()`); + console.log('视口', vw, ':', JSON.stringify(info)); +} +await send('Emulation.clearDeviceMetricsOverride'); +await sleep(300); +await shot('persona-card-final'); + +console.log('DONE'); +ws.close(); +process.exit(0); diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-personapad.mjs b/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-personapad.mjs new file mode 100644 index 0000000..4646ce6 --- /dev/null +++ b/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-personapad.mjs @@ -0,0 +1,70 @@ +// 验证「账号设定」内容区左右 20px 间隔(persona-wrap padding 0 20px) +const list = await fetch('http://localhost:9223/json').then((r) => r.json()); +let page = list.find((t) => t.type === 'page' && t.url.includes('localhost:8899')); +if (!page) { console.log('ERR: no workbuddy page open'); process.exit(1); } +const ws = new WebSocket(page.webSocketDebuggerUrl); +let id = 0; const pending = new Map(); +const send = (method, params = {}) => new Promise((res, rej) => { const i = ++id; pending.set(i, { res, rej }); ws.send(JSON.stringify({ id: i, method, params })); }); +ws.onmessage = (e) => { const m = JSON.parse(e.data); if (m.id && pending.has(m.id)) { const p = pending.get(m.id); pending.delete(m.id); m.error ? p.rej(new Error(m.error.message)) : p.res(m.result); } }; +await new Promise((r) => (ws.onopen = r)); +const ev = async (expr) => { const r = await send('Runtime.evaluate', { expression: expr, returnByValue: true, awaitPromise: true }); if (r.exceptionDetails) return { __err: (r.exceptionDetails.exception?.description || r.exceptionDetails.text).slice(0, 300) }; return r.result.value; }; +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); +await send('Page.enable'); await send('Runtime.enable'); + +const goto = async (url) => { + await send('Page.navigate', { url }); + await sleep(2500); + await send('Page.reload', { ignoreCache: true }); + await sleep(3000); +}; + +console.log('=== 旧梦留声机 1629(有卡片)==='); +await goto('http://localhost:8899/#/account/1629'); +const tabClick = await ev(`(() => { + const b = document.querySelector('[data-tab="persona"]'); + if (!b) return { err: 'no persona tab' }; + b.click(); + return 'clicked persona tab'; +})()`); +console.log('切tab:', JSON.stringify(tabClick)); +await sleep(1800); + +const s1 = await ev(`(() => { + const wrap = document.querySelector('.persona-wrap'); + const card = document.querySelector('.card-view'); + const img = card && card.querySelector('img'); + if (!wrap) return { err: 'no .persona-wrap' }; + const cs = getComputedStyle(wrap); + const dbody = document.querySelector('.detail-body'); + const dw = dbody ? dbody.getBoundingClientRect().width : null; + const ww = wrap.getBoundingClientRect().width; + const iw = img ? img.getBoundingClientRect().width : null; + return { + paddingLeft: cs.paddingLeft, + paddingRight: cs.paddingRight, + detailBodyW: dw, + wrapW: ww, + imgW: iw, + imgInset: iw !== null && dw !== null ? (dw - iw).toFixed(1) : null, // 卡片比内容区窄多少(两侧总和) + }; +})()`); +console.log('persona padding 检查:', JSON.stringify(s1, null, 2)); + +// 切文字版后再看文字容器 +const s2 = await ev(`(() => { + const btn = document.querySelector('.persona-vs-icons [data-vs="text"]'); + if (btn) btn.click(); + return 'switched to text'; +})()`); +await sleep(600); +const s3 = await ev(`(() => { + const md = document.querySelector('.persona-text'); + if (!md) return { err: 'no .persona-text' }; + const dbody = document.querySelector('.detail-body'); + const dw = dbody ? dbody.getBoundingClientRect().width : null; + const mw = md.getBoundingClientRect().width; + return { mdW: mw, inset: dw !== null ? (dw - mw).toFixed(1) : null }; +})()`); +console.log('文字版检查:', JSON.stringify(s3, null, 2)); +ws.close(); +console.log('DONE'); diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-ranknarrow.mjs b/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-ranknarrow.mjs new file mode 100644 index 0000000..3dfd729 --- /dev/null +++ b/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-ranknarrow.mjs @@ -0,0 +1,58 @@ +// 验证周榜数值列「窄 + 右对齐」:功能页 #/ranking + 首页预览表,只读测量 +const list = await fetch('http://localhost:9223/json').then((r) => r.json()); +let page = list.find((t) => t.type === 'page' && t.url.includes('localhost:8899')); +if (!page) { console.log('ERR: no workbuddy page open'); process.exit(1); } +const ws = new WebSocket(page.webSocketDebuggerUrl); +let id = 0; const pending = new Map(); +const send = (method, params = {}) => new Promise((res, rej) => { const i = ++id; pending.set(i, { res, rej }); ws.send(JSON.stringify({ id: i, method, params })); }); +ws.onmessage = (e) => { const m = JSON.parse(e.data); if (m.id && pending.has(m.id)) { const p = pending.get(m.id); pending.delete(m.id); m.error ? p.rej(new Error(m.error.message)) : p.res(m.result); } }; +await new Promise((r) => (ws.onopen = r)); +const ev = async (expr) => { const r = await send('Runtime.evaluate', { expression: expr, returnByValue: true, awaitPromise: true }); if (r.exceptionDetails) return { __err: (r.exceptionDetails.exception?.description || r.exceptionDetails.text).slice(0, 300) }; return r.result.value; }; +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); +await send('Page.enable'); await send('Runtime.enable'); +const goto = async (hash) => { await send('Page.navigate', { url: 'http://localhost:8899/' + hash }); await sleep(1500); await send('Page.reload', { ignoreCache: true }); await sleep(3000); }; + +// ===== 1) 功能页 #/ranking ===== +await goto('#/ranking'); +const rankPage = await ev(`(() => { + const ths = [...document.querySelectorAll('.table th')]; + const wideKeys = ['comprehensiveScore','followers','newFans','newLikes','newComments','newShares']; + const colInfo = ths.map((th) => { + const rect = th.getBoundingClientRect(); + return { key: th.dataset.sort || '', label: th.textContent.trim().slice(0,8), wide: th.classList.contains('th-wide'), w: Math.round(rect.width), thAlign: getComputedStyle(th).textAlign }; + }); + // td 对齐抽查:第一个数据行 + const row = document.querySelector('.table tbody tr'); + const tds = row ? [...row.children] : []; + const tdAligns = tds.map((td) => td.className + ':' + getComputedStyle(td).textAlign); + return { colInfo, tdAligns, rowCount: document.querySelectorAll('.table tbody tr').length }; +})()`); +console.log('【功能页 #/ranking】'); +for (const c of rankPage.colInfo) { + if (c.wide) console.log(` ${(c.key||c.label).padEnd(20)} w=${String(c.w).padStart(4)} thAlign=${c.thAlign}`); +} +console.log(' td 对齐:', rankPage.tdAligns.join(' | ')); +const wideCols = rankPage.colInfo.filter((c) => c.wide); +const rankOk = wideCols.length === 6 && wideCols.every((c) => c.w <= 100 && c.thAlign === 'right') && rankPage.tdAligns.every((t) => !t.startsWith('num') || t.endsWith('right')); + +// ===== 2) 首页预览表 ===== +await goto('#/'); +const home = await ev(`(() => { + const row = document.querySelector('#rankPreviewList .rank-row:not(.rank-head)'); + if (!row) return { err: 'no row' }; + const W = (sel) => Math.round(row.querySelector(sel).getBoundingClientRect().width); + const A = (sel) => getComputedStyle(row.querySelector(sel)).textAlign; + return { + score: { w: W('.rk-score'), align: A('.rk-score') }, + fans: { w: W('.rk-num:nth-child(5)'), align: A('.rk-num:nth-child(5)') }, + likes: { w: W('.rk-num:nth-child(6)'), align: A('.rk-num:nth-child(6)') }, + comments: { w: W('.rk-num:nth-child(7)'), align: A('.rk-num:nth-child(7)') }, + shares: { w: W('.rk-num:nth-child(8)'), align: A('.rk-num:nth-child(8)') }, + }; +})()`); +console.log('【首页预览表】'); +console.log(' ', JSON.stringify(home)); +const homeOk = ['score','fans','likes','comments','shares'].every((k) => home[k] && home[k].w <= 85 && home[k].align === 'right'); + +console.log(rankOk && homeOk ? 'PASS' : 'CHECK: 功能页=' + rankOk + ' 首页=' + homeOk); +ws.close(); diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-rankpreview.mjs b/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-rankpreview.mjs new file mode 100644 index 0000000..be0eada --- /dev/null +++ b/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-rankpreview.mjs @@ -0,0 +1,49 @@ +// 验证「首页账号周榜预览」5 数值列加宽 + 功能列按钮「分析」→「关注」:只读测量 +const list = await fetch('http://localhost:9223/json').then((r) => r.json()); +let page = list.find((t) => t.type === 'page' && t.url.includes('localhost:8899')); +if (!page) { console.log('ERR: no workbuddy page open'); process.exit(1); } +const ws = new WebSocket(page.webSocketDebuggerUrl); +let id = 0; const pending = new Map(); +const send = (method, params = {}) => new Promise((res, rej) => { const i = ++id; pending.set(i, { res, rej }); ws.send(JSON.stringify({ id: i, method, params })); }); +ws.onmessage = (e) => { const m = JSON.parse(e.data); if (m.id && pending.has(m.id)) { const p = pending.get(m.id); pending.delete(m.id); m.error ? p.rej(new Error(m.error.message)) : p.res(m.result); } }; +await new Promise((r) => (ws.onopen = r)); +const ev = async (expr) => { const r = await send('Runtime.evaluate', { expression: expr, returnByValue: true, awaitPromise: true }); if (r.exceptionDetails) return { __err: (r.exceptionDetails.exception?.description || r.exceptionDetails.text).slice(0, 300) }; return r.result.value; }; +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); +await send('Page.enable'); await send('Runtime.enable'); + +// 1) 导航首页 + 强制刷新(hash 变化不重载文档) +await send('Page.navigate', { url: 'http://localhost:8899/#/' }); +await sleep(1500); +await send('Page.reload', { ignoreCache: true }); +await sleep(3500); + +// 2) 测量首页预览表各列宽 + 功能列按钮 +const check = await ev(`(() => { + const head = document.querySelector('#rankPreviewList .rank-head'); + if (!head) return { err: 'no rank preview', html: (document.getElementById('rankPreviewList') || {}).innerHTML ? 'has content' : 'empty' }; + const row = document.querySelector('#rankPreviewList .rank-row:not(.rank-head)'); + const W = (sel) => { const el = row && row.querySelector(sel); return el ? Math.round(el.getBoundingClientRect().width) : null; }; + const cols = [...head.children]; + const actBtn = row ? row.querySelector('.rk-act .btn') : null; + const headTxt = [...head.children].map((c) => c.textContent.trim()); + return { + headCols: headTxt, + widths: { score: W('.rk-score'), fans: W('.rk-num:nth-child(5)'), likes: W('.rk-num:nth-child(6)'), comments: W('.rk-num:nth-child(7)'), shares: W('.rk-num:nth-child(8)') }, + actBtnText: actBtn ? actBtn.textContent.trim() : null, + actBtnTitle: actBtn ? actBtn.title : null, + actBtnHref: actBtn ? (actBtn.getAttribute('href') || null) : null, + rowCount: document.querySelectorAll('#rankPreviewList .rank-row').length - 1, + dataCardsStill: !!document.querySelector('.data-cards'), + statsStill: !!document.getElementById('homeStats'), + }; +})()`); +console.log(JSON.stringify(check, null, 1)); + +// 3) 校验:5 列 >= 80px 且按钮为「关注」 +const widths = check.widths || {}; +const colsOk = ['score', 'fans', 'likes', 'comments', 'shares'].every((k) => widths[k] >= 80); +const btnOk = check.actBtnText === '关注' && (check.actBtnTitle || '').includes('关注'); +console.log('列宽达标(>=80):', colsOk, '| 按钮为关注:', btnOk); +console.log(colsOk && btnOk ? 'PASS' : 'CHECK'); + +ws.close(); diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-ranktabs.mjs b/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-ranktabs.mjs new file mode 100644 index 0000000..27f197b --- /dev/null +++ b/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-ranktabs.mjs @@ -0,0 +1,51 @@ +// 验证:首页周榜 tab 切换(账号周榜/作品热榜/点赞日榜/涨粉日榜,后 3 个占位) +const list = await fetch('http://localhost:9223/json').then((r) => r.json()); +let page = list.find((t) => t.type === 'page' && t.url.includes('localhost:8899')); +if (!page) { console.log('ERR: no workbuddy page open'); process.exit(1); } +const ws = new WebSocket(page.webSocketDebuggerUrl); +let id = 0; const pending = new Map(); +const send = (method, params = {}) => new Promise((res, rej) => { const i = ++id; pending.set(i, { res, rej }); ws.send(JSON.stringify({ id: i, method, params })); }); +ws.onmessage = (e) => { const m = JSON.parse(e.data); if (m.id && pending.has(m.id)) { const p = pending.get(m.id); pending.delete(m.id); m.error ? p.rej(new Error(m.error.message)) : p.res(m.result); } }; +await new Promise((r) => (ws.onopen = r)); +const ev = async (expr) => { const r = await send('Runtime.evaluate', { expression: expr, returnByValue: true, awaitPromise: true }); if (r.exceptionDetails) return { __err: (r.exceptionDetails.exception?.description || r.exceptionDetails.text).slice(0, 200) }; return r.result.value; }; +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); +await send('Page.enable'); await send('Runtime.enable'); + +await send('Page.navigate', { url: 'http://localhost:8899/#/' }); await sleep(1500); +await send('Page.reload', { ignoreCache: true }); await sleep(3000); + +// 初始状态 + 逐个点击 tab +const states = []; +const initial = await ev(`(() => ({ + tabs: [...document.querySelectorAll('.rank-tab')].map((t) => ({ txt: t.textContent.trim(), active: t.classList.contains('active') })), + toolsHidden: document.getElementById('rankTools').hidden, + listHidden: document.getElementById('rankPreviewList').hidden, + phHidden: document.getElementById('rankPlaceholder').hidden, +}))()`); +states.push({ clicked: '(初始)', ...initial }); + +for (const name of ['作品热榜', '点赞日榜', '涨粉日榜', '账号周榜']) { + const s = await ev(`(() => { + const t = [...document.querySelectorAll('.rank-tab')].find((x) => x.textContent.trim() === ${JSON.stringify(name)}); + t.click(); + return { + active: [...document.querySelectorAll('.rank-tab')].find((x) => x.classList.contains('active')).textContent.trim(), + toolsHidden: document.getElementById('rankTools').hidden, + listHidden: document.getElementById('rankPreviewList').hidden, + phHidden: document.getElementById('rankPlaceholder').hidden, + phText: document.getElementById('rankPlaceholder').textContent.trim(), + }; + })()`); + await sleep(150); + states.push({ clicked: name, ...s }); +} +console.log(JSON.stringify(states, null, 1)); + +const ok = states[0].tabs.length === 4 + && states[0].tabs[0].active && !states[0].toolsHidden && !states[0].listHidden && states[0].phHidden + && states[1].clicked === '作品热榜' && states[1].toolsHidden && states[1].listHidden && !states[1].phHidden && states[1].phText.includes('建设中') + && states[2].clicked === '点赞日榜' && states[2].toolsHidden && states[2].phText.includes('建设中') + && states[3].clicked === '涨粉日榜' && states[3].phText.includes('建设中') + && states[4].clicked === '账号周榜' && !states[4].toolsHidden && !states[4].listHidden && states[4].phHidden; +console.log(ok ? 'PASS' : 'CHECK'); +ws.close(); diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-rankwide.mjs b/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-rankwide.mjs new file mode 100644 index 0000000..6df4ed8 --- /dev/null +++ b/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-rankwide.mjs @@ -0,0 +1,56 @@ +// 验证「账号周榜」5 个数值列加宽(.th-wide min-width:110px) +// 只读测量,零副作用 +const list = await fetch('http://localhost:9223/json').then((r) => r.json()); +let page = list.find((t) => t.type === 'page' && t.url.includes('#/ranking')); +if (!page) page = list.find((t) => t.type === 'page' && t.url.includes('localhost:8899')); +if (!page) { console.log('ERR: no workbuddy page open'); process.exit(1); } +const ws = new WebSocket(page.webSocketDebuggerUrl); +let id = 0; const pending = new Map(); +const send = (method, params = {}) => new Promise((res, rej) => { const i = ++id; pending.set(i, { res, rej }); ws.send(JSON.stringify({ id: i, method, params })); }); +ws.onmessage = (e) => { const m = JSON.parse(e.data); if (m.id && pending.has(m.id)) { const p = pending.get(m.id); pending.delete(m.id); m.error ? p.rej(new Error(m.error.message)) : p.res(m.result); } }; +await new Promise((r) => (ws.onopen = r)); +const ev = async (expr) => { const r = await send('Runtime.evaluate', { expression: expr, returnByValue: true, awaitPromise: true }); if (r.exceptionDetails) return { __err: (r.exceptionDetails.exception?.description || r.exceptionDetails.text).slice(0, 300) }; return r.result.value; }; +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); +await send('Page.enable'); await send('Runtime.enable'); + +// 1) 导航到周榜页(若不在)并刷新加载最新 CSS +if (!page.url.includes('#/ranking')) { + await send('Page.navigate', { url: 'http://localhost:8899/#/ranking' }); + await sleep(2000); +} +await send('Page.reload', { ignoreCache: true }); +await sleep(3000); + +// 2) 测量表头各列宽度 +const cols = await ev(`(() => { + const ths = [...document.querySelectorAll('.table th')]; + const wideKeys = ['comprehensiveScore', 'newFans', 'newLikes', 'newComments', 'newShares']; + const rowCount = document.querySelectorAll('.table tbody tr').length; + const result = ths.map((th) => { + const rect = th.getBoundingClientRect(); + return { key: th.dataset.sort || '', label: th.textContent.trim().slice(0, 8), wide: th.classList.contains('th-wide'), w: Math.round(rect.width) }; + }); + return { rowCount, result }; +})()`); +console.log('行数:', cols.rowCount); +console.log('表头列宽:'); +for (const c of cols.result) { + const mark = c.wide ? ' [th-wide]' : ''; + const match = c.wide ? (c.w >= 110 ? ' >=110 ✓' : ' <110 ✗') : ''; + console.log(' ' + (c.key || c.label).padEnd(22) + ' width=' + String(c.w).padStart(4) + mark + match); +} +const wideCols = cols.result.filter((c) => c.wide); +const allOk = wideCols.length === 5 && wideCols.every((c) => c.w >= 110); + +// 3) 记录 CSS 规则是否生效 +const cssCheck = await ev(`(() => { + const sheet = [...document.styleSheets].map((s) => s.href || 'inline').join(','); + const th = document.querySelector('.table th.th-wide'); + const cs = th ? getComputedStyle(th) : null; + return { sheet, minWidth: cs ? cs.minWidth : 'n/a' }; +})()`); +console.log('样式源:', cssCheck.sheet.slice(0, 160)); +console.log('computed min-width:', cssCheck.minWidth); + +ws.close(); +console.log(allOk ? 'PASS: 5 列均已加宽(>=110px)' : 'CHECK: 存在未达标的列'); diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-rename.mjs b/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-rename.mjs new file mode 100644 index 0000000..3712ee2 --- /dev/null +++ b/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-rename.mjs @@ -0,0 +1,41 @@ +// 验证:「AI写作复盘」→「AI脚本诊断」全量改名 + 首页周榜列表底色 +const list = await fetch('http://localhost:9223/json').then((r) => r.json()); +let page = list.find((t) => t.type === 'page' && t.url.includes('localhost:8899')); +if (!page) { console.log('ERR: no workbuddy page open'); process.exit(1); } +const ws = new WebSocket(page.webSocketDebuggerUrl); +let id = 0; const pending = new Map(); +const send = (method, params = {}) => new Promise((res, rej) => { const i = ++id; pending.set(i, { res, rej }); ws.send(JSON.stringify({ id: i, method, params })); }); +ws.onmessage = (e) => { const m = JSON.parse(e.data); if (m.id && pending.has(m.id)) { const p = pending.get(m.id); pending.delete(m.id); m.error ? p.rej(new Error(m.error.message)) : p.res(m.result); } }; +await new Promise((r) => (ws.onopen = r)); +const ev = async (expr) => { const r = await send('Runtime.evaluate', { expression: expr, returnByValue: true, awaitPromise: true }); if (r.exceptionDetails) return { __err: (r.exceptionDetails.exception?.description || r.exceptionDetails.text).slice(0, 200) }; return r.result.value; }; +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); +await send('Page.enable'); await send('Runtime.enable'); +const goto = async (hash) => { await send('Page.navigate', { url: 'http://localhost:8899/' + hash }); await sleep(1500); await send('Page.reload', { ignoreCache: true }); await sleep(3000); }; + +// 1) 首页:数据预览 label + 快捷卡片 + 周榜列表底色 + 残留检查 +await goto('#/'); +const home = await ev(`(() => { + const stats = [...document.querySelectorAll('#homeStats .home-stat-item')].map((i) => i.querySelector('.home-stat-label').textContent); + const cards = [...document.querySelectorAll('.data-card')].map((c) => c.querySelector('.data-card-title').textContent); + const rk = document.getElementById('rankPreviewList'); + const rkBg = rk ? getComputedStyle(rk).backgroundColor : null; + const bodyTxt = document.body.textContent; + return { stats, cards, rkBg, residualOld: bodyTxt.includes('AI写作复盘'), residualNew: bodyTxt.includes('AI脚本诊断') }; +})()`); +console.log('【首页】'); +console.log(' 数据预览 label:', home.stats.join(' / ')); +console.log(' 快捷卡片:', home.cards.join(' / ')); +console.log(' 周榜列表底色:', home.rkBg); +console.log(' 残留旧名:', home.residualOld, '| 出现新名:', home.residualNew); + +// 2) 功能页 #/reviews:页面标题 +await goto('#/reviews'); +const rv = await ev(`(() => { + const t = document.querySelector('.page-title'); + return { title: t ? t.textContent : null, hasOld: document.body.textContent.includes('AI写作复盘') }; +})()`); +console.log('【功能页 #/reviews】页面标题:', rv.title, '| 残留旧名:', rv.hasOld); + +ws.close(); +const ok = !home.residualOld && home.residualNew && home.rkBg && home.rkBg !== 'rgba(0, 0, 0, 0)' && rv.title === 'AI脚本诊断' && !rv.hasOld; +console.log(ok ? 'PASS' : 'CHECK'); diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-rosegold.mjs b/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-rosegold.mjs new file mode 100644 index 0000000..5463c66 --- /dev/null +++ b/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-rosegold.mjs @@ -0,0 +1,45 @@ +// 验证王微斯卡片玫瑰金背景渲染(断言渐变 stop + 背景上文字提亮 + 截图) +const list = await fetch('http://localhost:9223/json').then((r) => r.json()); +let page = list.find((t) => t.type === 'page' && (decodeURIComponent(t.url).includes('王微斯') || t.url.includes('8899'))); +if (!page) { console.log('ERR: no wangweisi/8899 page open'); process.exit(1); } +const ws = new WebSocket(page.webSocketDebuggerUrl); +let id = 0; const pending = new Map(); +const send = (method, params = {}) => new Promise((res, rej) => { const i = ++id; pending.set(i, { res, rej }); ws.send(JSON.stringify({ id: i, method, params })); }); +ws.onmessage = (e) => { const m = JSON.parse(e.data); if (m.id && pending.has(m.id)) { const p = pending.get(m.id); pending.delete(m.id); m.error ? p.rej(new Error(m.error.message)) : p.res(m.result); } }; +await new Promise((r) => (ws.onopen = r)); +const ev = async (expr) => { const r = await send('Runtime.evaluate', { expression: expr, returnByValue: true, awaitPromise: true }); if (r.exceptionDetails) return { __err: (r.exceptionDetails.exception?.description || r.exceptionDetails.text).slice(0, 300) }; return r.result.value; }; +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); +await send('Page.enable'); await send('Runtime.enable'); + +const url = 'file:///C:/Users/maidou/Desktop/MCNSkill项目/王微斯/王微斯账号设定卡.svg'; +await send('Page.navigate', { url }); +await sleep(1200); +await send('Page.reload', { ignoreCache: true }); +await sleep(2500); + +const check = await ev(`(() => { + const g = document.getElementById('bgGrad'); + const stops = g ? g.querySelectorAll('stop') : []; + const titles = [...document.querySelectorAll('text')].filter((t) => t.textContent.includes('▍')).map((t) => t.getAttribute('fill')); + const deco = [...document.querySelectorAll('text')].find((t) => t.textContent.includes('channels requirement'))?.getAttribute('fill'); + const copy = [...document.querySelectorAll('text')].filter((t) => t.textContent.includes('数据来源')).map((t) => t.getAttribute('fill')); + const arrows = [...document.querySelectorAll('line')].map((l) => [Number(l.getAttribute('x1')), Number(l.getAttribute('x2'))]); + return { + bg0: stops[0]?.getAttribute('stop-color'), + bg1: stops[1]?.getAttribute('stop-color'), + titles, + deco, + copy, + arrows, + arrowsInGap: arrows.every(([a, b]) => b - a >= 11), // 线长≥11px(原 2px 贴卡) + whiteCards: document.querySelectorAll('rect[fill="#FFFFFF"]').length, + pinkChips: document.querySelectorAll('rect[fill="#F7E8F2"]').length + }; +})()`); +console.log(JSON.stringify(check, null, 2)); + +const r = await send('Page.captureScreenshot', { format: 'png' }); +const fs = await import('node:fs'); +fs.writeFileSync('shot-wangweisi-rosegold.png', Buffer.from(r.data, 'base64')); +console.log('截图完成: shot-wangweisi-rosegold.png'); +ws.close(); diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-tabstyle.mjs b/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-tabstyle.mjs new file mode 100644 index 0000000..b3c3ce2 --- /dev/null +++ b/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-tabstyle.mjs @@ -0,0 +1,42 @@ +// 验证:首页周榜 tab 选中样式 = 浅底色(primary-soft) + 2px 主色下划线,非选中透明 +const list = await fetch('http://localhost:9223/json').then((r) => r.json()); +let page = list.find((t) => t.type === 'page' && t.url.includes('localhost:8899')); +if (!page) { console.log('ERR: no workbuddy page open'); process.exit(1); } +const ws = new WebSocket(page.webSocketDebuggerUrl); +let id = 0; const pending = new Map(); +const send = (method, params = {}) => new Promise((res, rej) => { const i = ++id; pending.set(i, { res, rej }); ws.send(JSON.stringify({ id: i, method, params })); }); +ws.onmessage = (e) => { const m = JSON.parse(e.data); if (m.id && pending.has(m.id)) { const p = pending.get(m.id); pending.delete(m.id); m.error ? p.rej(new Error(m.error.message)) : p.res(m.result); } }; +await new Promise((r) => (ws.onopen = r)); +const ev = async (expr) => { const r = await send('Runtime.evaluate', { expression: expr, returnByValue: true, awaitPromise: true }); if (r.exceptionDetails) return { __err: (r.exceptionDetails.exception?.description || r.exceptionDetails.text).slice(0, 200) }; return r.result.value; }; +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); +await send('Page.enable'); await send('Runtime.enable'); + +await send('Page.navigate', { url: 'http://localhost:8899/#/' }); await sleep(1500); +await send('Page.reload', { ignoreCache: true }); await sleep(3000); + +const stylesOf = `(sel) => { + const el = document.querySelector(sel); + if (!el) return null; + const cs = getComputedStyle(el); + return { bg: cs.backgroundColor, color: cs.color, border: cs.borderBottomWidth + ' ' + cs.borderBottomStyle + ' ' + cs.borderBottomColor, weight: cs.fontWeight }; +}`; + +const states = []; +// 初始 active = 账号周榜 +states.push({ clicked: '(初始)', active: '账号周榜', style: await ev(`(${stylesOf})('.rank-tab.active')`) }); +// 记录非 active 的样式(作品热榜) +states.push({ clicked: '(初始)', note: '非active:作品热榜', style: await ev(`(${stylesOf})([...document.querySelectorAll('.rank-tab')].find(x => x.textContent.trim() === '作品热榜'))`) }); + +for (const name of ['作品热榜', '点赞日榜', '涨粉日榜', '账号周榜']) { + await ev(`(() => { [...document.querySelectorAll('.rank-tab')].find((x) => x.textContent.trim() === ${JSON.stringify(name)}).click(); })()`); + await sleep(150); + states.push({ clicked: name, active: await ev(`([...document.querySelectorAll('.rank-tab')].find(x => x.classList.contains('active'))?.textContent.trim() || null)`), style: await ev(`(${stylesOf})('.rank-tab.active')`) }); +} +console.log(JSON.stringify(states, null, 1)); + +// 预期:active bg=rgb(234, 241, 254) color=rgb(47, 111, 237) border=2px solid rgb(47, 111, 237) weight=600 +const isActiveStyle = (s) => s && s.bg === 'rgb(234, 241, 254)' && s.color === 'rgb(47, 111, 237)' && s.border === '2px solid rgb(47, 111, 237)' && s.weight === '600'; +const isIdleStyle = (s) => s && s.bg === 'rgba(0, 0, 0, 0)' && s.border === '2px solid rgba(0, 0, 0, 0)'; +const ok = states.every((s) => s.active === s.clicked) && isActiveStyle(states[0].style) && isIdleStyle(states[1].style) && states.slice(2).every((s) => isActiveStyle(s.style)); +console.log(ok ? 'PASS' : 'CHECK'); +ws.close(); diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-wheel.mjs b/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-wheel.mjs new file mode 100644 index 0000000..504fd39 --- /dev/null +++ b/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-wheel.mjs @@ -0,0 +1,36 @@ +const list = await fetch('http://localhost:9223/json').then((r) => r.json()); +const page = list.find((t) => t.type === 'page' && t.url.includes('#/rewrites')); +const ws = new WebSocket(page.webSocketDebuggerUrl); +let id = 0; const pending = new Map(); +const send = (method, params = {}) => new Promise((res, rej) => { const i = ++id; pending.set(i, { res, rej }); ws.send(JSON.stringify({ id: i, method, params })); }); +ws.onmessage = (e) => { const m = JSON.parse(e.data); if (m.id && pending.has(m.id)) { const p = pending.get(m.id); pending.delete(m.id); m.error ? p.rej(new Error(m.error.message)) : p.res(m.result); } }; +await new Promise((r) => (ws.onopen = r)); +const ev = async (expr) => { const r = await send('Runtime.evaluate', { expression: expr, returnByValue: true, awaitPromise: true }); if (r.exceptionDetails) return { __err: (r.exceptionDetails.exception?.description || r.exceptionDetails.text).slice(0, 300) }; return r.result.value; }; +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); +await send('Page.enable'); await send('Runtime.enable'); +await send('Page.reload', { ignoreCache: true }); +await sleep(2000); +await ev(`document.querySelector('[data-detail]').click()`); +await sleep(1200); +// 拿 AI脚本列 body 中心坐标 +const rect = await ev(`(() => { + const ai = [...document.querySelectorAll('.rw-stack > .rw-sec')].find((s) => s.querySelector('.rw-sec-head')?.textContent === 'AI脚本'); + const r = ai.querySelector('.rw-sec-body').getBoundingClientRect(); + return { x: Math.round(r.left + r.width / 2), y: Math.round(r.top + 200), top: Math.round(r.top), h: Math.round(r.height) }; +})()`); +console.log('aiBody rect:', JSON.stringify(rect)); +// 真实滚轮:向下滚 6 次(每次 -300 逻辑上内容上移?deltaY 正=向下滚动) +for (let i = 0; i < 8; i++) { + await send('Input.dispatchMouseEvent', { type: 'mouseWheel', x: rect.x, y: rect.y, deltaX: 0, deltaY: 300 }); + await sleep(120); +} +await sleep(300); +const st = await ev(`(() => { + const secs = [...document.querySelectorAll('.rw-stack > .rw-sec')]; + const aiBody = secs.find((s) => s.querySelector('.rw-sec-head')?.textContent === 'AI脚本').querySelector('.rw-sec-body'); + const sbBody = secs.find((s) => s.querySelector('.rw-sec-head')?.textContent === 'AI分镜提示词').querySelector('.rw-sec-body'); + const vis = [...sbBody.querySelectorAll('.sb-block')].find((b) => b.getBoundingClientRect().top >= sbBody.getBoundingClientRect().top - 2); + return { aiScrollTop: Math.round(aiBody.scrollTop), sbScrollTop: Math.round(sbBody.scrollTop), sbVis: vis ? (vis.querySelector('.sb-head')?.textContent.trim() || '').slice(0, 28) : null }; +})()`); +console.log('滚轮后:', JSON.stringify(st)); +process.exit(0); diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-wheel2.mjs b/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-wheel2.mjs new file mode 100644 index 0000000..6371e1b --- /dev/null +++ b/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-wheel2.mjs @@ -0,0 +1,34 @@ +const list = await fetch('http://localhost:9223/json').then((r) => r.json()); +const page = list.find((t) => t.type === 'page' && t.url.includes('#/rewrites')); +const ws = new WebSocket(page.webSocketDebuggerUrl); +let id = 0; const pending = new Map(); +const send = (method, params = {}) => new Promise((res, rej) => { const i = ++id; pending.set(i, { res, rej }); ws.send(JSON.stringify({ id: i, method, params })); }); +ws.onmessage = (e) => { const m = JSON.parse(e.data); if (m.id && pending.has(m.id)) { const p = pending.get(m.id); pending.delete(m.id); m.error ? p.rej(new Error(m.error.message)) : p.res(m.result); } }; +await new Promise((r) => (ws.onopen = r)); +const ev = async (expr) => { const r = await send('Runtime.evaluate', { expression: expr, returnByValue: true, awaitPromise: true }); if (r.exceptionDetails) return { __err: (r.exceptionDetails.exception?.description || r.exceptionDetails.text).slice(0, 200) }; return r.result.value; }; +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); +await send('Page.enable'); await send('Runtime.enable'); +// 弹窗可能还在(上轮遗留),先清理再开 +await ev(`document.querySelectorAll('.modal-mask').forEach((m) => m.remove()); document.querySelector('[data-detail]') && document.querySelector('[data-detail]').click()`); +await sleep(1500); +const rect = await ev(`(() => { + const ai = [...document.querySelectorAll('.rw-stack > .rw-sec')].find((s) => s.querySelector('.rw-sec-head')?.textContent === 'AI脚本'); + if (!ai) return null; + const r = ai.querySelector('.rw-sec-body').getBoundingClientRect(); + return { x: Math.round(r.left + r.width / 2), y: Math.round(r.top + 300) }; +})()`); +console.log('rect:', JSON.stringify(rect)); +for (let i = 0; i < 5; i++) { + await send('Input.dispatchMouseEvent', { type: 'mouseWheel', x: rect.x, y: rect.y, deltaX: 0, deltaY: 400 }); + await sleep(80); +} +await sleep(400); +const st = await ev(`(() => { + const secs = [...document.querySelectorAll('.rw-stack > .rw-sec')]; + const aiBody = secs.find((s) => s.querySelector('.rw-sec-head')?.textContent === 'AI脚本').querySelector('.rw-sec-body'); + const sbBody = secs.find((s) => s.querySelector('.rw-sec-head')?.textContent === 'AI分镜提示词').querySelector('.rw-sec-body'); + const vis = [...sbBody.querySelectorAll('.sb-block')].find((b) => b.getBoundingClientRect().top >= sbBody.getBoundingClientRect().top - 2); + return { aiScrollTop: Math.round(aiBody.scrollTop), sbScrollTop: Math.round(sbBody.scrollTop), sbVis: vis ? (vis.querySelector('.sb-head')?.textContent.trim() || '').slice(0, 28) : null }; +})()`); +console.log('滚轮后:', JSON.stringify(st)); +process.exit(0); diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/dsh-data.js b/project/短视频脚本创作/V1.0/mcn-work-shop/dsh-data.js index 68b5418..91f3eb5 100644 --- a/project/短视频脚本创作/V1.0/mcn-work-shop/dsh-data.js +++ b/project/短视频脚本创作/V1.0/mcn-work-shop/dsh-data.js @@ -225,12 +225,22 @@ function getReviewFull(rewriteId) { function getStats() { const d = getDb(); if (!d) return null; const c = (sql) => d.prepare(sql).get().c; + // AI写作复盘 = 已完成的 AI 脚本复盘数(script_review type='ai') + let reviews = 0; + try { reviews = c(`SELECT COUNT(*) c FROM script_review WHERE type='ai' AND status='done'`); } catch (e) { /* 统计失败不影响其他 */ } + // AI写作评分 = 最新 20 条 AI 复盘总分的平均(保留 1 位小数;无评分返回 null) + let writingScore = null; + try { + const rows = d.prepare(`SELECT total FROM script_review WHERE type='ai' AND status='done' AND total IS NOT NULL AND total <> '' ORDER BY updated_time DESC, id DESC LIMIT 20`).all(); + const nums = rows.map((r) => Number(r.total)).filter((v) => Number.isFinite(v)); + if (nums.length > 0) writingScore = Math.round((nums.reduce((s, v) => s + v, 0) / nums.length) * 10) / 10; + } catch (e) { /* 评分统计失败不影响其他统计 */ } return { accounts: c(`SELECT COUNT(*) c FROM hot_accounts WHERE del_flag=0`), analyses: c(`SELECT COUNT(*) c FROM account_video_analysis`), rewrites: c(`SELECT COUNT(*) c FROM rewrite_log WHERE status='done' AND script_text IS NOT NULL AND script_text <> ''`), - reviews: c(`SELECT COUNT(*) c FROM script_review WHERE status='done'`), - writingScore: c(`SELECT COUNT(*) c FROM script_review WHERE type='ai' AND status='done'`), + reviews, + writingScore, }; } diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/public/app.js b/project/短视频脚本创作/V1.0/mcn-work-shop/public/app.js index bdf62a3..639958a 100644 --- a/project/短视频脚本创作/V1.0/mcn-work-shop/public/app.js +++ b/project/短视频脚本创作/V1.0/mcn-work-shop/public/app.js @@ -100,30 +100,24 @@ function confirmSaveAccount(name, url, btn) { }); } -// 导入外部账号数据(移植自 dsh 插件「热门账号 → 导入」): -// 方式一:抖音账号信息(名称/抖音号/地址)→ AI 网页采集账号信息与视频列表入库 -// 方式二:本地文件夹绝对路径 → AI 读取目录分析文件结构后入库 +// 添加外部账号数据(移植自 dsh 插件「热门账号 → 导入」): +// 填写抖音账号信息(名称/地址)→ AI 网页采集账号信息与视频列表入库 // 提交复用 executeTask 任务链路(/api/run → 左侧会话栏可见 → 完成自动刷新) function openImportAccounts(btn) { const mask = document.createElement('div'); mask.className = 'modal-mask'; mask.innerHTML = ` `; document.body.appendChild(mask); @@ -131,20 +125,13 @@ function openImportAccounts(btn) { mask.addEventListener('click', (e) => { if (e.target === mask) close(); }); mask.querySelector('[data-cancel]').addEventListener('click', close); mask.querySelector('[data-ok]').addEventListener('click', () => { - const dir = (mask.querySelector('[data-imp-dir]').value || '').trim(); const name = (mask.querySelector('[data-imp-name]').value || '').trim(); - const did = (mask.querySelector('[data-imp-did]').value || '').trim(); const url = (mask.querySelector('[data-imp-url]').value || '').trim(); - if (!dir && !name && !did && !url) { showToast('请填写抖音账号信息或文件夹路径'); return; } - let prompt; - if (dir) { - prompt = `请帮我把本地文件夹「${dir}」中的账号数据导入:读取目录、分析文件结构,确认后写入账号列表`; - } else { - const parts = [name ? `账号名称「${name}」` : '', did ? `抖音号「${did}」` : '', url ? `账号地址「${url}」` : ''].filter(Boolean).join(','); - prompt = `请帮我把抖音账号 ${parts} 导入:通过抖音网页获取账号信息与视频列表,写入账号列表并保存到本地`; - } + if (!name && !url) { showToast('请填写账号名称或账号地址'); return; } + const parts = [name ? `账号名称「${name}」` : '', url ? `账号地址「${url}」` : ''].filter(Boolean).join(','); + const prompt = `请帮我把抖音账号 ${parts} 添加:通过抖音网页获取账号信息与视频列表,写入账号列表并保存到本地`; close(); - executeTask({ prompt, label: '导入指令', btn }); + executeTask({ prompt, label: '添加指令', btn }); }); } @@ -168,9 +155,11 @@ const ICON_PATHS = { 'file-code': '', 'table': '', 'file': '', + 'image': '', 'arrow-left': '', 'x': '', 'zap': '', + 'user-plus': '', 'download': '', }; @@ -298,10 +287,17 @@ function renderHome() { { icon: 'trending-up', title: '账号周榜', desc: '三方数据 · 赛道/日期筛选', hash: '#/ranking' }, { icon: 'users', title: '账号列表', desc: '关注的账号 + 视频列表', hash: '#/accounts' }, { icon: 'pen', title: 'AI写脚本', desc: 'AI生成脚本+分镜提示词', hash: '#/rewrites' }, - { icon: 'flask', title: 'AI写作复盘', desc: '检查清单+复盘报告', hash: '#/reviews' }, + { icon: 'flask', title: 'AI脚本诊断', desc: '检查清单+复盘报告', hash: '#/reviews' }, ]; return `
+
+
–热门账号
+
–视频解析
+
–AI写脚本
+
–AI脚本诊断
+
–AI写作评分
+
${cards.map((c) => `
-

账号周榜

-
+
+ + + + +
+
@@ -320,6 +321,7 @@ function renderHome() {
加载中…
+

AI生成脚本

查看全部
@@ -328,6 +330,29 @@ function renderHome() {
`; } +/* ================= 首页 数据预览(顶部统计条,参考 dsh-plugin-mcn 首页 stats) ================= */ +// 挂载时拉取一次(后端 SQLite 直查,无缓存),失败时保留占位「–」 +async function loadHomeStats() { + const el = $('#homeStats'); + if (!el) return; + try { + const d = await api('/api/dsh/stats'); + const s = d.stats || {}; + const items = [ + ['热门账号', s.accounts], + ['视频解析', s.analyses], + ['AI写脚本', s.rewrites], + ['AI脚本诊断', s.reviews], + ['AI写作评分', s.writingScore], + ]; + el.innerHTML = items.map(([label, value]) => + `
${value ?? '–'}${label}
` + ).join(''); + } catch (e) { + el.innerHTML = `
–数据不可用
`; + } +} + /* ================= 帮助页(功能帮助 + 快速提问) ================= */ function renderHelp() { return ` @@ -442,7 +467,7 @@ async function loadRankingPreview() { ${esc(r.newLikes ?? '-')} ${esc(r.newComments ?? '-')} ${esc(r.newShares ?? '-')} - ${r.inDb && r.dbId ? `查看` : ``} + ${r.inDb && r.dbId ? `查看` : ``}
`).join('') : '暂无周榜数据'; listEl.querySelectorAll('[data-rk-import]').forEach((b) => b.addEventListener('click', () => { confirmSaveAccount(b.dataset.rkName, b.dataset.rkUrl || '', b); @@ -600,7 +625,7 @@ function openSettings() { renderRootList(); $('#settingsModal').hidden = false; function closeSettings() { $('#settingsModal').hidden = true; } /* ================= 路由 ================= */ -const CRUMB_TITLES = { 'ranking': '账号周榜', 'accounts': '账号列表', 'rewrites': 'AI写脚本', 'reviews': 'AI写作复盘' }; +const CRUMB_TITLES = { 'ranking': '账号周榜', 'accounts': '账号列表', 'rewrites': 'AI写脚本', 'reviews': 'AI脚本诊断' }; function parseHash() { const h = (location.hash || '#/').replace(/^#/, ''); @@ -622,9 +647,20 @@ async function router() { updateCrumbs(''); view.innerHTML = renderHome(); view.querySelectorAll('.data-card').forEach((b) => b.addEventListener('click', () => { location.hash = b.dataset.hash; })); + // 周榜 tab 切换:账号周榜显示列表+工具条,其余 3 个新榜先占位 + view.querySelectorAll('.rank-tab').forEach((t) => t.addEventListener('click', () => { + view.querySelectorAll('.rank-tab').forEach((x) => x.classList.toggle('active', x === t)); + const week = t.dataset.rankTab === 'week'; + view.querySelector('#rankTools').hidden = !week; + view.querySelector('#rankPreviewList').hidden = !week; + const ph = view.querySelector('#rankPlaceholder'); + ph.hidden = week; + if (!week) ph.textContent = `「${t.textContent.trim()}」建设中,敬请期待`; + })); + loadHomeStats(); loadRankingPreview(); loadRecentScripts(); - view._reload = () => { loadRankingPreview(); loadRecentScripts(); }; + view._reload = () => { loadHomeStats(); loadRankingPreview(); loadRecentScripts(); }; return; } if (name === 'help') { @@ -645,7 +681,7 @@ async function router() { } // 数据功能页 if (typeof window.DataPages !== 'undefined') { - const titles = { ranking: '账号周榜', accounts: '账号列表', account: '账号详情', video: '视频详情', rewrites: 'AI写脚本', reviews: 'AI写作复盘' }; + const titles = { ranking: '账号周榜', accounts: '账号列表', account: '账号详情', video: '视频详情', rewrites: 'AI写脚本', reviews: 'AI脚本诊断' }; const pages = { 'ranking': () => window.DataPages.renderRanking(view), 'accounts': () => window.DataPages.renderAccounts(view), 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 f85b5af..3099299 100644 --- a/project/短视频脚本创作/V1.0/mcn-work-shop/public/data-pages.js +++ b/project/短视频脚本创作/V1.0/mcn-work-shop/public/data-pages.js @@ -132,17 +132,18 @@ async function renderRanking(view) { async function load() { const d = await api('/api/dsh/ranking?date=' + encodeURIComponent(state.date) + '&category=' + encodeURIComponent(state.category) + '&sortBy=' + state.sortBy + '&order=' + state.order + '&page=' + state.page + '&pageSize=' + state.pageSize); const di = state.dates.indexOf(state.date); - const sortable = (key, label) => `${label}${state.sortBy === key ? (state.order === 'asc' ? ' ↑' : ' ↓') : ''}`; + const WIDE = new Set(['comprehensiveScore', 'followers', 'newFans', 'newLikes', 'newComments', 'newShares']); // 数值列(窄+右对齐) + const sortable = (key, label) => `${label}${state.sortBy === key ? (state.order === 'asc' ? ' ↑' : ' ↓') : ''}`; const rows = (d.items || []).map((r) => ` ${r.rank ?? '-'} ${esc(r.category || '-')} ${r.profileUrl ? `${esc(r.accountName)}` : esc(r.accountName)} - ${fmt(r.comprehensiveScore)} - ${fmt(r.followers)} - ${fmt(r.newFans)} - ${fmt(r.newLikes)} - ${fmt(r.newComments)} - ${fmt(r.newShares)} + ${fmt(r.comprehensiveScore)} + ${fmt(r.followers)} + ${fmt(r.newFans)} + ${fmt(r.newLikes)} + ${fmt(r.newComments)} + ${fmt(r.newShares)} ${r.inDb ? `查看` : ``} `).join(''); view.innerHTML = ` @@ -161,7 +162,7 @@ async function renderRanking(view) {
- +
${sortable('rank', '排名')}${sortable('comprehensiveScore', '综合得分')}${sortable('followers', '粉丝')}${sortable('newFans', '新增粉丝')}${sortable('newLikes', '新增点赞')}${sortable('newComments', '新增评论')}${sortable('newShares', '新增分享')} @@ -224,7 +225,7 @@ async function renderAccounts(view) { - + @@ -272,11 +273,16 @@ async function renderAccountDetail(view, id) { const d = await api('/api/dsh/account-videos?accountId=' + accountId + '&page=1&pageSize=20').catch((e) => ({ ok: false, error: e.message })); if (!d.ok) { view.innerHTML = `
${toolbarBack()}
${esc(d.error || '账号不存在')}
`; bindBack(view); return; } const acc = d.account; - let persona = null, analysis = null; + let persona = null, analysis = null, personaCard = null; + let viewMode = 'card'; // 账号设定展示模式:card(卡片版,默认)/ text(文字版) async function loadPersona() { - const r = await api('/api/dsh/account-persona?accountId=' + accountId); - persona = r.content || null; + const [r, c] = await Promise.all([ + api('/api/dsh/account-persona?accountId=' + accountId), + api('/api/dsh/account-card?name=' + encodeURIComponent(acc.account_name || '')), + ]); + persona = (r && r.content) || null; + personaCard = (c && c.exists && c.path) ? { path: c.path } : null; } async function loadAnalysis() { const r = await api('/api/dsh/account-analysis?accountId=' + accountId); @@ -313,12 +319,30 @@ async function renderAccountDetail(view, id) {
赛道账号功能
${rows || ''}
发布时间视频ID视频标题点赞评论分享时长操作
暂无视频数据
${pagerHtml(dv.total, dv.page, 20, (p) => { page = p; load(); })}`; } else if (tab === 'persona') { - panel = persona ? `
${renderMarkdown(persona)}
` : `
-
${svgIcon('users', 40)}
-
该账号暂无账号设定信息
-
为达人提炼账号设定后,可基于设定生成脚本
- -
`; + if (!persona) { + panel = `
+
${svgIcon('users', 40)}
+
该账号暂无账号设定信息
+
为达人提炼账号设定后,可基于设定生成脚本
+ +
`; + } else if (!personaCard) { + // 无卡片:直接文字版(无切换控件) + panel = `
${renderMarkdown(persona)}
`; + } else { + // 有卡片:右上角图标切换(卡片版 / 文字版),默认卡片版 + panel = ` +
+
+ + +
+
+ ${esc(acc.account_name)}账号设定卡 +
+
${renderMarkdown(persona)}
+
`; + } } else { panel = analysis ? `
${renderMarkdown(analysis)}
` : `
该账号暂无数据分析
`; } @@ -341,6 +365,15 @@ async function renderAccountDetail(view, id) { const open = text.classList.toggle('open'); b.textContent = open ? '收起' : '展开'; })); + // 账号设定:卡片版 / 文字版 切换(直接 DOM 切换,不重渲染,保留滚动位置) + view.querySelectorAll('[data-vs]').forEach((b) => b.addEventListener('click', () => { + viewMode = b.dataset.vs; + view.querySelectorAll('[data-vs]').forEach((x) => x.classList.toggle('vi-active', x.dataset.vs === viewMode)); + const cardEl = view.querySelector('.card-view'); + const mdEl = view.querySelector('.persona-text'); + if (cardEl) cardEl.classList.toggle('vs-hidden', viewMode !== 'card'); + if (mdEl) mdEl.classList.toggle('vs-hidden', viewMode !== 'text'); + })); view.querySelectorAll('[data-rewrite]').forEach((b) => b.addEventListener('click', () => { const r = JSON.parse(b.dataset.rewrite); const parsed = !!r.parsed; @@ -427,7 +460,7 @@ async function renderRewrites(view) { ${fmt(v.publish_time)} ${(v.has_source || v.has_analysis) ? '✅已解析' : '未解析'} ${v.rewrite_count > 0 ? `` : '—'} - + `).join(''); view.innerHTML = `
@@ -580,7 +613,7 @@ async function renderRewrites(view) { await load(); } -/* ================= AI写作复盘 ================= */ +/* ================= AI脚本诊断 ================= */ async function renderReviews(view) { const d = await api('/api/dsh/rewrites').catch((e) => ({ ok: false, error: e.message })); const DIMS = ['设定承接', '框架节奏', '钩子设置', '叙事逻辑', '极致事件', '共鸣和共情', '其他', '总分']; @@ -624,7 +657,7 @@ async function renderReviews(view) { view.innerHTML = `
-
${toolbarBack()}AI写作复盘按生成时间倒序 · 共 ${items.length} 条
+
${toolbarBack()}AI脚本诊断按生成时间倒序 · 共 ${items.length} 条
${DIMS.map((h) => ``).join('')}${rows || ''} diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/public/style.css b/project/短视频脚本创作/V1.0/mcn-work-shop/public/style.css index 74980bf..e6a7d87 100644 --- a/project/短视频脚本创作/V1.0/mcn-work-shop/public/style.css +++ b/project/短视频脚本创作/V1.0/mcn-work-shop/public/style.css @@ -235,7 +235,7 @@ main { height: calc(100vh - 54px); } .root-add input { flex: 1; padding: 8px 10px; border: 1px solid var(--border); border-radius: 8px; font-size: 15px; } .root-msg { margin-top: 8px; font-size: 14px; color: var(--accent); min-height: 16px; } .modal-actions { text-align: right; margin-top: 14px; } -/* 导入账号弹窗:方式一账号信息 / 方式二本地文件夹 */ +/* 添加账号弹窗:账号信息(名称/地址) */ .import-form .f-row { margin-bottom: 10px; } .import-form label { display: block; font-size: 13px; color: var(--dim); margin-bottom: 4px; } .import-form .input { width: 100%; box-sizing: border-box; } @@ -265,6 +265,12 @@ main#view { height: calc(100vh - 54px); overflow: auto; } /* ================= 首页 ================= */ .home { max-width: 960px; margin: 0 auto; padding: 24px; } +/* 数据预览条(顶部统计,参考 dsh-plugin-mcn 首页 stats:浅蓝底横条 + 均分竖排数字/标签) */ +.home-stats { display: flex; width: 100%; box-sizing: border-box; background: rgba(47,111,237,.07); border-radius: 12px; padding: 20px 0; margin-bottom: 6px; } +.home-stat-item { flex: 1; min-width: 0; display: flex; flex-direction: column; align-items: center; gap: 5px; padding: 2px 12px; border-right: 1px solid rgba(47,111,237,.16); } +.home-stat-item:last-child { border-right: none; } +.home-stat-value { font-size: 26px; font-weight: 700; color: var(--primary); line-height: 1.2; font-variant-numeric: tabular-nums; } +.home-stat-label { font-size: 13px; color: var(--dim); } .data-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 14px; margin: 18px 0 24px; } .data-card { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; background: var(--panel); border: 1px solid var(--border); border-radius: 12px; padding: 16px 18px; cursor: pointer; text-align: left; box-shadow: var(--shadow); transition: all .15s; } .data-card:hover { border-color: var(--primary); transform: translateY(-2px); box-shadow: 0 4px 12px rgba(47,111,237,.15); } @@ -290,8 +296,8 @@ main#view { height: calc(100vh - 54px); overflow: auto; } .btn-primary { background: var(--primary); color: #fff; border-color: transparent; } .btn-primary:hover { background: #2a5fd0; color: #fff; } /* 功能列按钮区分:保存=浅灰底深字,查看=白底蓝字 */ -.btn-import { background: #f0f2f5; border-color: #e1e4e8; color: #495057; } -.btn-import:hover { background: #e4e7eb; border-color: #d0d4da; color: #343a40; } +.btn-import { background: #fff; border-color: #d0d4da; color: #495057; } +.btn-import:hover { background: var(--primary-soft); border-color: var(--primary); color: var(--primary); } .btn-view { background: #fff; border-color: var(--primary); color: var(--primary); } .btn-view:hover { background: var(--primary-soft); color: var(--primary); } .empty { padding: 30px; text-align: center; color: var(--dim); font-size: 15px; } @@ -306,6 +312,15 @@ main#view { height: calc(100vh - 54px); overflow: auto; } .cell-loc { max-width: 110px; } /* 账号定位列缩窄 */ .sortable { cursor: pointer; } .sortable:hover { color: var(--primary); } +.table th.th-wide { min-width: 90px; text-align: right; } /* 周榜数值列表头(综合得分/粉丝/新增粉丝/点赞/评论/分享):窄 + 右对齐(提特异性压过 .table th 默认左对齐) */ +.table td.num { text-align: right; font-size: 13px; font-variant-numeric: tabular-nums; padding-left: 8px; padding-right: 8px; } /* 数值列单元格:右对齐 + 紧凑(等宽数字,内容窄于 90px 让 min-width 生效) */ +/* 周榜表:fixed 布局固定列宽(auto 布局下 100% 表格富余空间会按比例摊薄拉宽列,min-width 压不住;fixed 下 width 为硬约束,账号列吃剩余) */ +.table.table-rank { table-layout: fixed; } +.table.table-rank th, .table.table-rank td { overflow: hidden; text-overflow: ellipsis; } +.table.table-rank th.th-wide { width: 90px; } +.table.table-rank th:nth-child(1) { width: 40px; } /* 排名 */ +.table.table-rank th:nth-child(2) { width: 90px; } /* 赛道 */ +.table.table-rank th:nth-child(10) { width: 85px; } /* 功能 */ .link { color: #4a90e2; cursor: pointer; text-decoration: none; } .link:hover { text-decoration: underline; } .link.strong { font-weight: 600; } @@ -329,6 +344,16 @@ main#view { height: calc(100vh - 54px); overflow: auto; } .td-id { font-size: 13px; font-variant-numeric: tabular-nums; padding-left: 8px; padding-right: 8px; } th.th-id { width: 120px; } +/* ================= 账号设定 卡片版/文字版 切换(右上角图标按钮组) ================= */ +.persona-wrap { position: relative; padding: 0 20px; } /* 账号设定内容左右各 20px 间隔 */ +.persona-vs-icons { position: absolute; top: 0; right: 0; z-index: 2; display: inline-flex; border: 1px solid var(--border); border-radius: 6px; overflow: hidden; background: var(--panel); } +.vi-btn { border: none; background: none; padding: 5px 8px; cursor: pointer; color: var(--dim); display: flex; align-items: center; } +.vi-btn + .vi-btn { border-left: 1px solid var(--border); } +.vi-btn:hover { color: var(--primary); } +.vi-btn.vi-active { background: var(--primary); color: #fff; } +.card-view img { width: 100%; height: auto; display: block; border-radius: 8px; } +.vs-hidden { display: none !important; } + /* ================= 信息卡 ================= */ .info-card { border: 1px solid var(--border); border-radius: 8px; padding: 14px 16px; margin-bottom: 14px; background: var(--panel); } /* 账号信息按行分组:行内项目按内容宽度左对齐,保留行间距 */ @@ -396,11 +421,16 @@ th.th-id { width: 120px; } /* ================= 首页 账号周榜 预览 ================= */ .rank-preview { margin: 4px 0 20px; } +.rank-tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--border); padding-bottom: 0; margin-bottom: 10px; align-self: stretch; } +.rank-tab { border: none; background: transparent; color: var(--dim); padding: 6px 12px 4px; border-radius: 8px 8px 0 0; cursor: pointer; font-size: 14px; border-bottom: 2px solid transparent; } +.rank-tab:hover { color: var(--primary); background: var(--primary-soft); } +.rank-tab.active { color: var(--primary); font-weight: 600; background: var(--primary-soft); border-bottom: 2px solid var(--primary); } +.rank-placeholder { border: 1px dashed var(--border); border-radius: 10px; padding: 36px 12px; text-align: center; color: var(--dim); background: var(--panel); font-size: 14px; } .rank-tools { display: flex; align-items: center; gap: 6px; margin-left: auto; } .rank-filter { padding: 3px 6px; font-size: 14px; max-width: 110px; } .rank-date-label { font-size: 14px; color: var(--dim); min-width: 62px; text-align: center; white-space: nowrap; } -.rank-preview-list { border: 1px solid var(--border); border-radius: 10px; overflow: hidden; } -.rank-row { display: grid; grid-template-columns: 34px 84px minmax(80px, 1fr) 52px repeat(4, 58px) 72px; align-items: center; gap: 6px; padding: 6px 10px; font-size: 14px; border-bottom: 1px solid var(--border); } +.rank-preview-list { border: 1px solid var(--border); border-radius: 10px; overflow: hidden; background: var(--panel); box-shadow: var(--shadow); } +.rank-row { display: grid; grid-template-columns: 34px 84px minmax(80px, 1fr) 76px repeat(4, 76px) 72px; align-items: center; gap: 6px; padding: 6px 10px; font-size: 14px; border-bottom: 1px solid var(--border); } .rank-row:last-child { border-bottom: none; } .rank-head { background: var(--panel); color: var(--dim); font-weight: 600; font-size: 13px; } .rk-no { color: var(--dim); font-weight: 600; } diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/server.js b/project/短视频脚本创作/V1.0/mcn-work-shop/server.js index c75f06d..a981536 100644 --- a/project/短视频脚本创作/V1.0/mcn-work-shop/server.js +++ b/project/短视频脚本创作/V1.0/mcn-work-shop/server.js @@ -55,6 +55,31 @@ function resolveSafe(rawPath) { return null; } +// ---- 查找账号设定卡 SVG ---- +// 产出规范:{产出根}/{达人昵称}/{达人昵称}账号设定卡.svg(功能六产出,与账号设定.md 同目录) +// 查找顺序:规范路径 → 根下直挂 → 账号目录内扫描(防目录名/账号名不一致) +function findAccountCard(name) { + const safeName = String(name || '').replace(/[\\/:*?"<>|]/g, '_').trim(); + if (!safeName) return null; + for (const root of allowedRoots()) { + const p1 = path.join(root, safeName, safeName + '账号设定卡.svg'); + const p2 = path.join(root, safeName + '账号设定卡.svg'); + for (const c of [p1, p2]) { + try { if (fs.existsSync(c) && fs.statSync(c).isFile()) return c; } catch (e) { /* 忽略 */ } + } + } + for (const root of allowedRoots()) { + const dir = path.join(root, safeName); + try { + if (fs.statSync(dir).isDirectory()) { + const hit = fs.readdirSync(dir).find((f) => f.toLowerCase().endsWith('.svg') && f.includes('账号设定卡')); + if (hit) return path.join(dir, hit); + } + } catch (e) { /* 忽略 */ } + } + return null; +} + // ---- 目录树(递归,跳过隐藏项)---- function buildTree(absPath, depth) { if (depth > 4) return null; @@ -206,6 +231,25 @@ const server = http.createServer((req, res) => { return; } + if (p === '/api/file') { + // 内联输出(无 attachment 头):供 / 直接渲染(如账号设定卡 SVG) + const safe = resolveSafe(q.get('path')); + if (!safe) return sendErr(403, '路径不在允许范围'); + try { + const st = fs.statSync(safe.abs); + if (!st.isFile()) return sendErr(404, '文件不存在'); + res.writeHead(200, { + 'Content-Type': mimeOf(safe.abs), + 'Content-Length': st.size, + 'Cache-Control': 'no-store', + }); + fs.createReadStream(safe.abs).pipe(res); + } catch (e) { + return sendErr(500, '读取失败: ' + e.message); + } + return; + } + // ---------- 数据功能(只读工作台自有数据库副本) ---------- const dshOk = (code, obj) => sendJSON(code, { ok: true, ...obj }); const dshGuard = (res2) => { if (!dsh.dbAvailable()) { sendJSON(200, { ok: false, error: '未检测到工作台数据库(mcn-plugin.db),本功能不可用' }); return true; } return false; }; @@ -257,6 +301,12 @@ const server = http.createServer((req, res) => { const r = dsh.getPersona(qInt('accountId', 0)); return dshOk(200, { content: r ? r.content : null, analysisTime: r ? r.analysis_time : null }); } + if (p === '/api/dsh/account-card') { + // 查询账号设定卡 SVG 文件(桌面产出,允许根目录下查找):{账号名}账号设定卡.svg + if (dshGuard()) return; + const hit = findAccountCard(q.get('name')); + return dshOk(200, { exists: !!hit, path: hit || null }); + } if (p === '/api/dsh/account-analysis') { if (dshGuard()) return; const r = dsh.getAccountAnalysis(qInt('accountId', 0)); diff --git a/project/短视频脚本创作/V1.0/subskill/mcn-dou-analysis/SKILL.md b/project/短视频脚本创作/V1.0/subskill/mcn-dou-analysis/SKILL.md index 69e3aca..43da189 100644 --- a/project/短视频脚本创作/V1.0/subskill/mcn-dou-analysis/SKILL.md +++ b/project/短视频脚本创作/V1.0/subskill/mcn-dou-analysis/SKILL.md @@ -12,7 +12,7 @@ description: "MCN 达人爆款视频解析与人设卡生成技能:达人账 ## 功能总览 -本技能包含五大功能,**各功能独立成 MD 文件**(部署时按需手动删除不需要的功能文件即可禁用,无需改代码): +本技能包含六大功能,**各功能独立成 MD 文件**(部署时按需手动删除不需要的功能文件即可禁用,无需改代码): | # | 功能 | 说明 | 对应步骤 | 功能文件(删除即禁用) | |---|------|------|----------|----------------------| @@ -21,6 +21,7 @@ description: "MCN 达人爆款视频解析与人设卡生成技能:达人账 | 3 | 提炼账号设定 | 基于视频 MCP 解析数据,生成人设卡(内容创作维度) | Step 3-6 | `references/feature/03_提炼账号设定.md` | | 4 | 分析视频数据 | 基于近4个月 Excel 数据,每月最好/最差各1条综合分析(商业/市场维度) | Step 7 | `references/feature/04_分析视频数据.md` | | 5 | 解析视频 | 对**单个视频**进行 MCP 解析(上传→查询→保存 content.json/analysis.json),不依赖功能三筛选流程;由账号详情视频列表「解析视频」触发 | 独立 | `references/feature/05_解析视频.md` | +| 6 | 生成账号设定卡片 | 基于功能三产出的 `{达人昵称}账号设定.md`,生成**竖版长图 SVG 信息图**(账号设定卡):深色/浅色长图风、模块映射 12 段人设卡、含内容基因/角色类型库/情绪图/红线清单等模块 | **功能三自动联动(默认)**:产出设定后自动生成,无需单独要求 | `references/feature/06_生成账号设定卡片.md` | **部署禁用说明**:删除某功能 MD 即禁用对应功能,可连带删除该功能的支撑文件: - 功能一:`references/浏览器搜索抖音账号操作规范.md`(功能一/二共用,**两个功能都删除时**才可连带删除) @@ -28,6 +29,7 @@ description: "MCN 达人爆款视频解析与人设卡生成技能:达人账 - 功能三:`references/人设卡生成方法.md`、`references/知识库/账号设定/`、`references/解析结果格式规范.md`(**如功能四/五仍启用,解析结果格式规范需保留**,功能四目录结构以其为准) - 功能四:`references/账号数据分析方法.md`、`references/知识库/视频拆解/` - 功能五:解析结果格式规范(**如功能三/四仍启用需保留**) +- 功能六:无连带文件(依赖功能三产出的账号设定.md 作输入,但功能三删了也可单独对有存量账号设定的账号生成卡片)。**默认联动**:功能三产出账号设定后自动生成设定卡;删除功能六文件则功能三产出后不再自动生成(存量账号仍可手动要求生成) --- @@ -83,6 +85,8 @@ description: "MCN 达人爆款视频解析与人设卡生成技能:达人账 | 统一JSON工具 | `scripts/json_tool.py`(create-content/create-analysis/inspect/verify-folders 四模式) | | 统一Excel工具 | `scripts/excel_tool.py`(select-top6/query/export-selected 三模式,含三比率计算;select-top6 默认数量按 SKILL.md「视频筛选标准」,可用 `--count N` 调整) | | 视频筛选脚本 | `scripts/select_top6.py`(已整合到 excel_tool.py,保留向后兼容) | +| 功能六流程 | `references/feature/06_生成账号设定卡片.md`(生成账号设定卡片五步执行:读设定→读模板样例→模块映射→SVG手绘→验证交付) | +| 账号设定卡模板样例 | `references/样例/`(最佳成品模板:`模板_旧梦留声机账号设定卡.svg` + 渲染参考 `参考_旧梦留声机_账号设定卡_长图版.png`;新账号复用版式骨架与样式常量,内容全替换) | | 产出路径配置 | `references/路径配置.md`(🔴 产出路径唯一权威配置:用户自定义路径最高优先,默认桌面 `账号分析/` 兜底) | | 增量库(references-add) | `references-add/`(③ 用户环境场景的增量库机制;当前①开发机/②dsh环境状态仅保留 README 机制说明,原增量已并入标准库:`references/路径配置.md` + `references/铁律避坑规则/`(含功能三`账号设定执行避坑.md` + 功能四`账号数据分析执行避坑.md`)) | @@ -131,6 +135,7 @@ description: "MCN 达人爆款视频解析与人设卡生成技能:达人账 - **Step 4**:`upload_douyin_video` 提交解析 → 等待 3-5 分钟 → `short_video_detail` 查询(content + analysis) - **Step 5**:`scripts/json_tool.py` 保存 content.json / analysis.json(格式规范见 `references/解析结果格式规范.md`) - **Step 6**:脚本创作技能 + 女娲造人术两个技能库综合生成人设卡(**12段结构**:账号核心定位→内容基因→类型库→情绪结构→视听风格→内容打法→内容规则→系列内容规划→差异化壁垒→红线清单→当前需解决的问题→设定分析说明,不含商业化变现路径分析) +- **Step 7(自动联动)**:人设卡 `{达人昵称}账号设定.md` 落盘后,**自动执行功能六生成 `{达人昵称}账号设定卡.svg`**(无需用户单独要求);设定后续被修改(内容/模块/命名)时,**卡片必须同步修改**(双向联动维护,详见 `references/feature/06_生成账号设定卡片.md`「双向联动维护」) ## 六、功能四:分析视频数据 @@ -141,9 +146,33 @@ description: "MCN 达人爆款视频解析与人设卡生成技能:达人账 - **两段式强制流程**:先逐条拆解(重度调用源技能,拆解文件单独保存到各视频文件夹)→ 拆解全部完成后才进入汇总分析(月度趋势/爆款共性/低表现共性/内容要素关联/差异化壁垒/商业化空间) - **产出**:`{达人昵称}账号数据分析.md`(8章结构:核心结论/五维诊断/月度趋势/最好视频/最差视频/差异化壁垒/商业化空间/行动建议)+ 各视频 `{视频标题}_拆解分析.md` +## 七、功能五:解析视频 + +> **完整执行流程见 `references/feature/05_解析视频.md`**(单视频 MCP 解析:上传→等待→查询→保存 JSON,不依赖功能三 TOP 筛选) + +核心流程概要: +- **触发**:账号详情视频列表「解析视频」按钮 / 用户指定单条视频 +- **流程**:`upload_douyin_video` 提交(3-5 分钟等待)→ `short_video_detail` 查询(content + analysis)→ `scripts/json_tool.py` 保存到 `{产出根目录}/{达人昵称}/视频对标/{视频文件夹}/` +- **可选写库**:环境有 MCN 插件接口时补录 `account_video_source` 并提取选题 + +## 八、功能六:生成账号设定卡片 + +> **完整执行流程见 `references/feature/06_生成账号设定卡片.md`**(读账号设定 → 模块映射 → SVG 手绘 → 验证交付) + +核心流程概要: +- **自动触发(默认)**:功能三产出 `{达人昵称}账号设定.md` 后**自动执行本功能**生成设定卡,无需用户单独要求;用户单独要求生成/调整时也可独立触发 +- **触发判定(先判定再执行)**:三问判定链(①有无账号设定→无则功能三或全链路 ②卡片是否已存在→有则询问覆盖/微调 ③设定模块是否完整→缺则裁剪适配),7 种情况处理详见 `references/feature/06_生成账号设定卡片.md`「触发场景处理」 +- **双向联动维护**:设定 md 与设定卡**同步修改**(任一边改动另一边必须跟随),规则详见 06 文档「双向联动维护」 +- **输入**:功能三产出的 `{达人昵称}账号设定.md`(人设卡 12 段) +- **产出**:`{达人昵称}账号设定卡.svg`(竖版长图信息图,`viewBox="0 0 680 ~1700"`,深色长图风),与账号设定.md 同目录 +- **方式**:读设定全文 → **读模板样例 `references/样例/模板_旧梦留声机账号设定卡.svg` + `参考_旧梦留声机_账号设定卡_长图版.png`(复用版式骨架与样式常量,内容全替换)** → 12 段模块映射 → 手写 SVG → 结构+坐标+间距三重校验 → present_files 预览确认 +- **模块裁剪适配**:按设定文件实际内容裁剪/增补模块(无流程删叙事结构、无角色库删角色库、基因按条数定列数),禁止照搬模板模块清单;每个保留模块须能在设定中找到原文 +- **配色适配**:**底色 = 整卡背景色**,跟随账号风格,禁止一律模板深棕红底(深棕红+浅米只是旧梦留声机风格);**底色分两系**——深沉/浓郁调性走深色系、明快/甜美/治愈调性走浅色系。按设定「色彩体系」真实色值 + 赛道归入背景色映射(暖怀旧→深棕红、亲子/治愈→深焦糖、科技/冷淡→深灰蓝、清新三农→深墨绿、潮流都市→深紫黑、活力运动→深海军、甜美/甜宠→深莓紫「深沉时」/ 玫瑰金 `#D6A3A9→#9A5563`「明快时,如王微斯定稿」…);深色系公式:主色压暗 15-25% 降饱和 → 深色渐变底;浅色系公式:主色提亮+降饱和至中浅明度 → 浅色渐变底。**底色决定色系**:深色系内部元素=浅米卡+深色字+珊瑚红强调,浅色系内部元素=白卡+粉边+深粉字+粉强调;背景上直接书写的文字随背景深浅提亮保可读。「色彩体系」色块用账号真实色值 +- **要点**:GENE 0X 小标签 + 角色英文代号;情绪图条形底边对齐、峰谷差 ≥40px;红线全量;文字垂直居中(y=中心);批量移动坐标用脚本统一加减防双重偏移 + --- -## 七、注意事项 +## 九、注意事项 1. **MCP解析等待**:新提交的视频解析需3-5分钟,不要立即查询。批量提交后统一等待再批量查询。 2. **analysis延迟**:`content`(分镜头表)通常先出,`analysis`(结构化分析)可能延迟1-2分钟。如只有content,稍后重试获取analysis。 diff --git a/project/短视频脚本创作/V1.0/subskill/mcn-dou-analysis/references/feature/03_提炼账号设定.md b/project/短视频脚本创作/V1.0/subskill/mcn-dou-analysis/references/feature/03_提炼账号设定.md index 38cce3a..7387b21 100644 --- a/project/短视频脚本创作/V1.0/subskill/mcn-dou-analysis/references/feature/03_提炼账号设定.md +++ b/project/短视频脚本创作/V1.0/subskill/mcn-dou-analysis/references/feature/03_提炼账号设定.md @@ -81,3 +81,18 @@ id = "{detailId}" - 女娲造人术 → `subskills/nuwa-skill-main/`(深度分析方法) **人设卡统一 12 段结构**:账号核心定位 → 账号内容基因 → 类型库(按账号类型适配)→ 情绪结构 → 视听风格 → 内容打法 → 内容规则 → 系列内容规划 → 差异化壁垒 → 红线清单 → 当前需解决的问题 → 设定分析说明。**人设卡聚焦内容创作本身(含差异化壁垒的内容侧"为什么别人做不了"),不含商业化变现路径分析**(商业化/市场分析归功能四账号数据分析)。 + +--- + +## Step 7:自动生成账号设定卡片(联动) + +> 完整方法见 `references/feature/06_生成账号设定卡片.md`(读设定 → 读模板样例 → 模块映射 → SVG 手绘 → 验证交付) + +**默认自动触发**:`{达人昵称}账号设定.md` 落盘后,**无需用户单独要求**,直接执行功能六生成 `{达人昵称}账号设定卡.svg`(与设定 md 同目录)。仅当功能六文件被删除(部署禁用)或用户明确「只要设定不要卡片」时跳过。 + +**双向联动维护(修改时两边一起修改)**: +- 设定 md 与设定卡是**同一账号信息的两种载体**,任何一方修改,另一方必须同步: + - **改设定**(内容/模块/命名)→ 卡片同步更新:内容级改动走功能六「方式」重新生成;单点改动(改文案/换底色)直接改 SVG 对应元素 + - **改卡片**(单点微调:换底色/微调布局)→ 设定 md 中对应内容同步(底色映射见 06 文档「配色适配」,内容以设定为源) + - **术语/模块改名** → 两边一起改(历史案例:「内容路线」→「叙事结构」、「核心气质」→「内容标签」均同步改设定 md + 卡片 SVG) +- **一致性校验**:交付前用 grep 核对两文件关键术语一致(模块名「叙事结构」「内容标签」「内容基因·TOP6」等),零残留后再交付 diff --git a/project/短视频脚本创作/V1.0/subskill/mcn-dou-analysis/references/feature/06_生成账号设定卡片.md b/project/短视频脚本创作/V1.0/subskill/mcn-dou-analysis/references/feature/06_生成账号设定卡片.md new file mode 100644 index 0000000..1e658cd --- /dev/null +++ b/project/短视频脚本创作/V1.0/subskill/mcn-dou-analysis/references/feature/06_生成账号设定卡片.md @@ -0,0 +1,228 @@ +# 功能六:生成账号设定卡片 + +> 本文件是 MCNDouAnalysis 技能**功能六(生成账号设定卡片)**的完整执行流程。 +> 独立文件设计目的:部署到用户电脑时,如不需要此功能,**删除本文件即可禁用**(本功能不依赖其他功能文件,无连带删除项;依赖功能三产出的 `{达人昵称}账号设定.md` 作为输入)。 +> 使用场景:**默认由功能三自动联动触发**——功能三产出 `{达人昵称}账号设定.md` 后自动生成本卡片(无需用户开口);用户单独要求生成/调整已有卡片时也可独立触发。产出为**竖版长图信息图**(SVG 设定卡),可视化展示账号定位/内容基因/情绪结构等核心信息。 + +--- + +## 输入 + +- `{达人昵称}账号设定.md`(功能三产出,人设卡 12 段结构:账号核心定位→内容基因→类型库→情绪结构→视听风格→内容打法→内容规则→系列内容规划→差异化壁垒→红线清单→当前需解决的问题→设定分析说明) +- 产出目录:与账号设定.md 同目录(`{产出根目录}/{达人昵称}/`,产出根目录以 `references/路径配置.md` 为准) + +## 产出 + +- `{达人昵称}账号设定卡.svg`(竖版长图信息图,默认 `viewBox="0 0 680 1700"` 量级,宽度响应式 100%) +- 同一目录可另存参考图:`{达人昵称}_账号设定卡_长图版.png`(导出格式,供快速预览/分享) + +--- + +## 触发场景处理(先判定再执行,勿直接开画) + +> **默认自动触发**:功能三产出账号设定后**自动执行本功能**(用户无感,直接落盘卡片);以下判定链适用于「用户单独要求生成/调整卡片」场景,或自动生成后的校验。 + +核心前置:**卡片 = f(账号设定.md)**。用户要求生成卡片时,先走判定链再分支处理: + +**判定链(三问)**: +``` +① 账号设定.md 有没有? + ├─ 没有 → 账号在达人账号表吗? + │ ├─ 在库 → 先功能三(提炼账号设定)→ 情况3 + │ └─ 不在 → 功能一→二→三→六 全链路 → 情况4 + └─ 有 → ② 卡片 svg 已存在吗? + ├─ 存在 → 询问覆盖 or 微调 → 情况2 / 7 + └─ 不存在 → ③ 设定模块完整吗? + ├─ 完整 → 全模块生成 → 情况1 + └─ 缺模块 → 裁剪适配生成 → 情况5 +``` + +| # | 情况 | 判定依据 | 处理方式 | +|---|------|---------|---------| +| 1 | 有设定 + 无卡片 | `{账号名}账号设定.md` 存在,`{账号名}账号设定卡.svg` 不存在 | **自动流程已覆盖**(功能三产出即生成);此处仅当自动生成被跳过(如部署禁用)时手动走「方式」全流程 | +| 2 | 有设定 + 已有卡片 | 卡片文件已存在 | **先询问用户**:重新生成覆盖 / 基于现有卡片微调(换底色/加模块/改布局) | +| 3 | 无设定(账号在库) | 达人账号表有该账号,但无设定文件 | **先走功能三**提炼账号设定(人设卡)→ 再生成卡片(两步串行) | +| 4 | 无设定(账号不在库) | 用户给的是全新账号 | **全链路**:功能一获取信息 → 功能二抓视频 → 功能三提炼设定 → 功能六生成卡片 | +| 5 | 设定不完整(缺模块) | 设定缺叙事结构/角色库/色彩体系等某段 | 正常生成 + **模块裁剪适配**(删对应模块,后续模块上移,不留空白) | +| 6 | 参考某账号样式 | 用户指定「像 XX 那样做」 | 读被参考账号的卡片做样式参考(版式+底色跟该账号风格),内容仍用本账号设定 | +| 7 | 调整已有卡片 | 用户说改现有卡片的某处 | **直接改 SVG 文件**(单点修改,不重走功能三/全流程) | + +**环境路径**:① 开发机 / ③ 用户环境 = `桌面/MCNSkill项目/{账号名}/`;② dsh 环境 = `D:\dshworkspace\解析任务\{达人昵称}/`(以 `references/路径配置.md` 为唯一权威)。 + +**双向联动维护(修改时两边一起修改)**: +- 设定 md 与设定卡 = **同一账号信息的两种载体**,任一边修改,另一边必须同步: + - **改设定**(内容/模块/命名)→ 卡片同步:内容级改动走下方「方式」重新生成;单点改动(改文案/换底色)直接改 SVG 对应元素 + - **改卡片**(单点微调:换底色/微调布局)→ 设定 md 对应内容同步(底色映射见上方「配色适配」,内容以设定为源) + - **术语/模块改名** → 两边一起改(历史案例:「内容路线」→「叙事结构」、「核心气质」→「内容标签」、「六大内容基因」→「内容基因·TOP6」,均同步改设定 md + 卡片 SVG) +- **一致性校验**:交付前用 grep 核对关键术语两文件一致(模块名「叙事结构」「内容标签」「内容基因·TOP6」等),零残留后再交付 + +--- + +## 方式(五步执行) + +### ① 读取账号设定 +通读 `{达人昵称}账号设定.md` 全文,提取全部模块内容(内容基因/类型库/情绪结构/红线等),**不允许跳读、不允许只挑标题**——卡片内容必须来自设定文件原文,可做金句化压缩,但不得自行编造数据。 + +### ①.5 读取模板样例(关键步骤,保证同款效果) +动手前**必须先读 `references/样例/模板_旧梦留声机账号设定卡.svg`**(最佳成品模板)与 `references/样例/参考_旧梦留声机_账号设定卡_长图版.png`(渲染效果参考): +- 以模板的**版式骨架/字号体系/卡片尺寸/模块间距**为基准,新账号卡片沿用同一套视觉语言;**配色不沿用模板**(模板底只是旧梦留声机风格),按下方「配色适配」章节随账号风格换底色 +- 模板是「旧梦留声机」实例(内容不可照搬),新账号只复用**版式骨架与样式常量**,内容全部替换 +- 模板如被反复复用后需整体迭代(配色/布局升级),改模板 → 同步生成一次旧梦留声机卡确认效果 → 再用于新账号 + +### ② 模块映射(对齐 12 段人设卡 → 信息图模块) +| # | 账号设定内容 | 信息图模块 | 布局 | +|---|------------|-----------|------| +| 1 | 账号核心定位(赛道/类型/定位语) | 顶部标题区(账号名 + 「账号设定卡」副题 + 定位语) | 浅米色大框 520×170,居中 | +| 2 | 内容打法中的路线/流程(如有六步闭环) | 叙事结构(流程条) | 6 卡横排,箭头串联 | +| 3 | 内容标签/核心标签 | 内容标签(chips) | 胶囊 86×32,两行 | +| 4 | 内容基因(功能三基于 TOP6 视频提炼的结论) | 内容基因·TOP6 | 3 列 × 2 行,卡 186×130 | +| 5 | 视听风格/色彩体系 | 色彩体系 | 5 色卡横排(色块 + 标签) | +| 6 | 类型库/角色类型 | 角色类型库 | 3 列 × 2 行,卡 186×130 | +| 7 | 情绪结构(六段式) | 情绪图(柱状条) | 7 条条形,底边对齐 | +| 8 | 红线清单 | 红线清单 | 2 列 × 3 行卡 | +| 9 | 差异化壁垒/其他亮点 | 可并入基因/类型卡描述行 | — | +| — | 底部版权 | 数据来源 + 版本注记 | 居中两行小字 | + +> **注**:内容基因条目由**功能三基于 TOP6 视频**(筛选权威参数见 SKILL.md「视频筛选标准」)提炼得出——TOP6 视频是分析依据(输入),内容基因是结论(输出),卡片上全量呈现、不裁剪。后续生成卡片统一以「内容基因·TOP6」描述该模块。 + +### ③ 排版绘制(SVG 手写) +逐模块写 SVG(无模板文件,直接手写 ``/``),坐标自上而下递增,模块间留白 ≥30px。 + +### ④ 验证交付 +- 结构校验:`` 配对、`` 配对、所有文字 x 不超卡片右缘 +- 坐标校验:条形底边对齐、卡片文字垂直居中(y = rect.y + rect.h/2) +- 间距校验:相邻模块最小间距 ≥16px,无重叠 +- 交付:present_files 打开预览,用户确认后再定稿 + +--- + +## 模块裁剪适配(不同账号内容不同,禁止照搬模板模块清单) + +模板的 9 个模块以旧梦留声机的设定内容为准。**新账号按设定文件实际有无对应内容裁剪/增补模块**,不得为了凑模块硬塞内容,也不得遗漏设定中已有的核心模块: + +| 设定文件内容 | 模块去留 | 处理 | +|------------|---------|------| +| 有叙事结构/六步闭环/流程 | 保留「叙事结构」 | 流程条按实际步骤数画(4/5/6 步皆可,等宽均分 600px 宽) | +| 无流程类内容 | **删除**「叙事结构」模块 | 后续模块上移,不留空白 | +| 有内容基因(≥3 条) | 保留「内容基因·TOP6」 | 按实际条数画 2 列或 3 列(卡宽 = 600px/列数 - 间距) | +| 有角色类型/角色库 | 保留「角色类型库」 | 按实际条数画 2 列或 3 列 | +| 无角色库 | **删除**「角色类型库」 | — | +| 有情绪结构/情绪曲线 | 保留「情绪图」 | 条形按实际段数画(4-7 段),底边对齐 | +| 有色彩体系 | 保留「色彩体系」 | 色块按实际色数画(3-6 块),色值用设定中的真实色值 | +| 有红线清单 | 保留「红线清单」 | 2 列排布,条数不限(2-6 条) | +| 有差异化壁垒/独特点 | 可并入基因卡描述行或单独卡 | — | + +**适配后校验**:每个保留模块必须能在设定文件中找到对应原文(grep 关键字验证);删除模块后 y 坐标整体上移,模块间间距保持 ≥30px。 + +--- + +## 配色适配(底色 = 背景色,跟随账号风格;内部元素保持模板统一) + +模板配色(深棕红渐变底 + 浅米卡)是旧梦留声机的「暖黄旧时光」色系,**只代表该账号风格,不是默认底色**。**新账号卡片只换底色(整卡背景),必须按账号风格匹配,禁止全部沿用深棕红底。底色分两系:深沉/浓郁调性走深色系(深色渐变底),明快/甜美/治愈调性走浅色系(浅色渐变底),内部元素跟随所选色系规范。** + +> **关键定义**:本节「底色」一律指**整卡背景色**(深色渐变底或浅色渐变底)。卡片块、文字、标签、强调色等**内部元素不随账号风格变**,统一按所选色系的「配色规范」表执行:**深色系(深色长图风)**= 浅米卡片 + 深色文字 + 珊瑚红强调;**浅色系(浅色甜宠风)**= 白卡 + 粉边 + 深粉文字 + 粉强调。同一账号系列卡片必须固定同一色系,保证系列视觉语言一致。 + +### ① 判定账号风格(读设定「色彩体系」+ 赛道/类型) +以账号设定.md 的「视听风格/色彩体系」模块真实色值为第一依据(主色/强调色),赛道与账号类型辅助判定,归入下表风格。 + +### ② 风格 → 背景色映射(示例,可扩展) +| 账号风格(色彩体系/赛道特征) | 背景底色(深色渐变) | +|----------------------------|--------------------| +| 暖怀旧/旧时光(剧情·怀旧,如旧梦留声机) | 深棕红 `#2B0F0A→#1A0805` | +| 温情/亲子/治愈(亲子Vlog) | 深焦糖 `#2B1A10→#1A0F08` | +| 科技/数码/冷淡(数码科技/商业) | 深灰蓝 `#0E1626→#080D16` | +| 清新/田园/三农(三农/户外) | 深墨绿 `#0F1F18→#08130E` | +| 潮流/都市/时尚(潮流风尚/颜值) | 深紫黑 `#17101F→#0C0810` | +| 活力/运动/热血(身体锻炼/体育) | 深海军 `#0E1B2E→#080F1A` | +| 甜美/少女/二次元(甜宠/乙女向) | 深莓紫 `#3A1E42→#22102A`(调性深沉时);**玫瑰金渐变 `#D6A3A9→#9A5563`(调性明快时,如王微斯·定稿)** | + +**背景色推导公式(表中未列的风格)**:取账号色彩体系主色 → 压暗至 15-25% 亮度 + 降饱和 → 得深色渐变底(**仅背景色**,内部元素色不变)。 + +**浅色系推导(明快/甜美/治愈调性)**:取账号色彩体系主色 → 提亮+降饱和至中浅明度(约 65-90% 亮度,如王微斯:主粉 → 玫瑰金 `#D6A3A9→#9A5563`)→ 得浅色系渐变底;正文文字用主色加深版(`#7B2D5E` 档)。**背景上直接书写的文字(模块标题/装饰线/注释/版权)随背景深浅提亮保可读**:背景越深文字越亮(如玫瑰金底上模块标题 `#FDE8EC`、装饰线/注释 `#EAC6CE`、斜体/版权 `#DDBCC4`)。 + +### ③ 强规则 +- **禁止**默认沿用模板深棕红底;同一账号每次生成的卡背景色保持一致(系列一致性 = 同账号一致,非跨账号一致) +- **底色决定色系**:选深色系则内部元素按「配色规范·深色系」执行,选浅色系则按「配色规范·浅色系」执行;同一色系内部元素色一律固定,不随账号再改 +- 模块「色彩体系」色块:必须用设定文件中账号的真实色值(模板 5 色块仅示例) +- 冲突时:以「看得清、有层次、账号辨识度」为最高准则,模板色优先兜底 + +--- + +## 要求 + +### 配色规范(色系内统一,所有账号通用;底色随账号风格选系) + +**深色系(深色长图风,深沉/浓郁调性,如旧梦留声机)** +| 元素 | 色值 | +|------|------| +| 背景 | 深棕红渐变 `#2B0F0A → #1A0805` | +| 顶部标题框 | 浅米色 `#FAEEDA`,圆角 14 | +| 主标题 | 渐变 `#8C2F1E → #C05A32`,28px 700 | +| 副标题(账号设定卡) | `#8C2F1E`,16px 600,letter-spacing 2 | +| 模块标题 | `#FAEEDA`,14px 700,前缀「▍」 | +| 卡片背景(内容类) | `#FAEEDA`,边框 `#EDCBA4` | +| 卡片背景(强调类) | `#FDEBE2`,边框 `#E8B7A2` | +| 卡片标题 | `#7B2D1B`(基因)/ `#8C3B2B`(角色)14px 700 | +| 卡片正文 | `#6F5B4A`,11px | +| 小标签(GENE 0X) | `#C13B2F`,10px 700,letter-spacing 1 | +| 英文代号(角色卡) | `#B0715A`,10px 斜体 | +| 情绪图核爆段 | `#D6403A` 底 + 白字 700 | +| 情绪图琥珀段 | `#F0B25C` / `#F7D9A8` | +| 情绪小字/注释 | `#A88770`,10px | +| 红线×号 | `#D6403A` 700 | + +**浅色系(浅色甜宠风,明快/甜美/治愈调性,如王微斯)** +| 元素 | 色值 | +|------|------| +| 背景 | 玫瑰金渐变 `#D6A3A9 → #9A5563`(王微斯定稿;更浅可回退浅粉紫 `#F8EDF6 → #F1E0EE`) | +| 顶部标题框 | 白 `#FFFFFF`,粉边 `#E8C9DC`,圆角 14 | +| 主标题 | 渐变 `#C0457F → #E876A9`,28px 700 | +| 副标题(账号设定卡) | `#7B2D5E`,16px 600,letter-spacing 2 | +| 模块标题(背景上) | `#FDE8EC`(玫瑰金底),14px 700,前缀「▍」 | +| 卡片背景(内容类) | 白 `#FFFFFF`,边框 `#E8C9DC` | +| 卡片背景(强调类/chips) | `#F7E8F2`,边框 `#D8A9C8` | +| 卡片标题 | `#7B2D5E`,14px 700 | +| 卡片正文 | `#6F4A5E`,11px | +| 小标签(GENE 0X) | `#E0437F`,10px 700,letter-spacing 1 | +| 英文代号(角色卡) | `#B98AA6`,10px 斜体 | +| 情绪图破防段 | `#E0437F` 底 + 白字 700 | +| 情绪图琥珀段 | `#E8B7D0` / `#F7E8F2` | +| 情绪小字/注释(背景上) | `#EAC6CE`,10px | +| 红线×号 | `#E0437F` 700 | + +> 背景上直接书写的装饰线/版权等元素取同色系提亮(`#EAC6CE`/`#DDBCC4`),随背景深浅微调保可读;背景越浅(如浅粉紫)越接近深粉字 `#7B2D5E`,背景越深(如玫瑰金)越接近浅粉白 `#FDE8EC`。 + +### 排版要求 +1. **文字垂直居中**:卡片内文字用 `dominant-baseline="central"` 且 y = 卡片中心;不用则手动算(y = rect.y + rect.h/2),禁止偏移 +2. **情绪图条形**:底边全部对齐同一基线(峰谷差 ≥ 40px,核爆段最高),文字在条形内居中 +3. **卡片文字不超界**:标题末字尾 ≤ 卡右缘 - 8px;中文按 12px/字、14px/字粗算宽度 +4. **角色卡英文代号**:大写首字母缩写 + 单词(如 `E. ELDER` / `M. SUFFERING`),贴合角色记忆符号 +5. **金句化压缩**:描述行可加 1-2 句传播性金句("沉默里把全家扛在肩上"式),但必须源自设定语义 + +--- + +## 格式 + +- 文件:`.svg`(可另导出 `.png`) +- 画布:`viewBox="0 0 680 {高度}"`(宽度固定 680,高度随内容约 1700 量级;内容增加则加高并同步 `height` 与背景 rect) +- 背景:全幅渐变 rect(首元素) +- 文字:`` 内联属性(font-size/font-weight/fill/letter-spacing),无外部字体依赖 +- 命名:`{达人昵称}账号设定卡.svg`,与 `{达人昵称}账号设定.md` 同目录 + +--- + +## 注意事项(执行避坑) + +1. **🔴 批量移动坐标防叠加偏移**:需整体下移某模块时,优先用脚本对该区域所有 `y="…"` 统一加减;若先手动改了一张卡又跑脚本,会造成该卡双重偏移(实测踩坑:手动改 y=1018→1028 后又脚本 +10 → 1038,需单独修正回 1028)。改完必须校验同排卡片 y 一致。 +2. **模块间最小间距 16px**:情绪小字与红线标题曾贴在一起(16px 内),文字行距需显式拉开(≥32px 为宜)。 +3. **红线清单全量**:6 条红线必须全部呈现,禁止压缩条数(实测重写时漏到 2 条)。 +4. **垂直居中靠计算不靠感觉**:`dominant-baseline="central"` 的 y 即中心线;不带该属性时文字基线偏下,需 y = 中心 + 字高/2 左右微调。 +5. **情绪图高度**:条形高度按用户要求可翻倍(峰顶 112px),底边对齐即可,翻倍后需同步下移注释行与后续模块。 +6. **交付前必须 present_files 预览**:用户基于预览提出微调(配色/字号/间距),逐条修改逐条验证,禁止批量猜测。 +7. **导出 PNG**:用户要 PNG 时用支持 SVG 渲染的工具导出(分辨率按 viewBox 宽 680 × 高度),文件放同目录。 + +--- + +## 完成汇报 + +向用户汇报:卡片文件路径、SVG 尺寸(viewBox)、模块数量、与账号设定.md 的一致性说明;如导出 PNG 一并汇报。 diff --git a/project/短视频脚本创作/V1.0/subskill/mcn-dou-analysis/references/人设卡生成方法.md b/project/短视频脚本创作/V1.0/subskill/mcn-dou-analysis/references/人设卡生成方法.md index 29802e1..6321670 100644 --- a/project/短视频脚本创作/V1.0/subskill/mcn-dou-analysis/references/人设卡生成方法.md +++ b/project/短视频脚本创作/V1.0/subskill/mcn-dou-analysis/references/人设卡生成方法.md @@ -46,7 +46,7 @@ | 账号类型 | 三分类判定结果(见 5.0) | 单一达人型 / 固定主角+轮换搭档型 / 群像型 + 一句判定依据 | | 核心受众 | 核心观看人群 | 年龄 + 性别 + 核心特征(场景/痛点/兴趣) | | 核心标签 | 区别于同类账号的标签词 | 2-3 个标签短语(如"第一视角沉浸 + 直球互动 + 反差萌破防") | -| 内容路线 | 内容推进路径/固定流程 | 用箭头串联的固定流程(如"赛道开场破冰 → 冲突推进 → 情绪升温 → 交心破防 → 收尾升华") | +| 叙事结构 | 内容推进路径/固定流程 | 用箭头串联的固定流程(如"赛道开场破冰 → 冲突推进 → 情绪升温 → 交心破防 → 收尾升华") | | 情绪锚点 | 观众获得的核心情绪体验 | 一句话(如"该账号 = 替观众完成 XX 的爽文体验") | > 账号名称写在文档标题(`# {达人昵称} 账号人设卡片`)。V2.0 的「变现模式」字段属商业化路径,**人设卡不写**(归功能四账号数据分析);「内容形式」「差异化定位」如需体现,并入赛道/核心标签中表达。 @@ -164,7 +164,7 @@ ``` 账号设定确认清单(按 12 段逐项核对): -① 一、账号核心定位 6 维度是否准确(赛道/账号类型/核心受众/核心标签/内容路线/情绪锚点) +① 一、账号核心定位 6 维度是否准确(赛道/账号类型/核心受众/核心标签/叙事结构/情绪锚点) ② 二、账号内容基因是否提炼到位(频道底色,观众3秒识别) ③ 三、类型库/达人设定:按账号类型适配(单一达人=一份6章节 / 固定主角+轮换搭档=主角6章节+搭档库+筛选标准 / 群像=内容基因+角色类型库);达人设定 6 章节是否完整覆盖(身份/性格/外在特质/价值观与底线/说话风格/行为决策) ④ 四、情绪结构是否覆盖(段落表+情绪曲线+情绪设计法则) @@ -204,7 +204,7 @@ 对照 5.1 的 12 段,人设卡按以下顺序排版: 1. **账号核心定位** - (表格:赛道/账号类型/核心受众/核心标签/内容路线/情绪锚点) + (表格:赛道/账号类型/核心受众/核心标签/叙事结构/情绪锚点) 2. **账号内容基因** (N个基因标记,观众3秒内认出"这是谁") diff --git a/project/短视频脚本创作/V1.0/subskill/mcn-dou-analysis/references/样例/参考_旧梦留声机_账号设定卡_长图版.png b/project/短视频脚本创作/V1.0/subskill/mcn-dou-analysis/references/样例/参考_旧梦留声机_账号设定卡_长图版.png new file mode 100644 index 0000000..a9c9987 Binary files /dev/null and b/project/短视频脚本创作/V1.0/subskill/mcn-dou-analysis/references/样例/参考_旧梦留声机_账号设定卡_长图版.png differ diff --git a/project/短视频脚本创作/V1.0/subskill/mcn-dou-analysis/references/样例/模板_旧梦留声机账号设定卡.svg b/project/短视频脚本创作/V1.0/subskill/mcn-dou-analysis/references/样例/模板_旧梦留声机账号设定卡.svg new file mode 100644 index 0000000..e6f7f51 --- /dev/null +++ b/project/短视频脚本创作/V1.0/subskill/mcn-dou-analysis/references/样例/模板_旧梦留声机账号设定卡.svg @@ -0,0 +1,252 @@ + +旧梦留声机 账号设定卡 · 长图版 +群像账号旧梦留声机的账号设定信息图:叙事结构、内容标签、内容基因·TOP6、色彩体系、角色类型库、情绪曲线、红线清单 + + + + + + + + + + + + + + + + + + +旧梦留声机 +账号设定卡 + +群像账号 · 情感走心短剧 · 公益正能量 · 品牌微电影 +把「被遗忘的人、被亏欠的人」的故事,讲给所有人听 + + +— channels requirement board 2026 — + + +▍叙事结构 · 六步闭环 + + +痛点开场 + +年代闪回 + +贵人破局 + +逆袭核爆 + +时空闭环 + +公益落版 + + + + + + + + + + +▍内容标签 + + +催泪核弹 + +隔辈亲情 + +底层逆袭 + +真实原型 + +公益落版 + +群像叙事 + + + +年代跨度 + +无声胜有声 + +反差叙事 + +细节伏笔 + +以小见大 + +朴素美学 + + + +▍内容基因·TOP6 + + + +GENE 01 +第一视角沉浸 +观众的眼睛 = 角色的眼睛 +门缝窥视 · 缸底仰视 · 晃动失焦 +让观众不在场,又无处不在 + + + +GENE 02 +年代跨度闪回 +年份角标 + 前后期画质断层 +2011→2025,时间即蓄力池 +越旧的年代,越催现在的泪 + + + +GENE 03 +真实感毛边 +老茧双手 · 污渍 · 方言 · 破碗 +越粗糙越真实,好看是表演 +脏一点,泪点多一点 + + + +GENE 04 +拉扯式情绪结构 +压抑→微光→破灭→再压抑→核爆 +每 10-30 秒一次小转折 +不让观众一次哭尽,留到核爆 + + + +GENE 05 +动作大于语言 +不说爱只做事——铺床 · 摔扫帚 +观众自己脑补的情感更狠 +一句台词压十秒,比哭更催泪 + + + +GENE 06 +破壁时刻 +高潮切真实原型史料 / 原音 +白芳礼式「你以为这是演的?」 +「这是真的」——观众瞬间塌房 + + + +▍色彩体系 · 冷暖对撞 + + +泛黄旧时光 + +冷灰乡土 + +暖黄回忆 + +冷白危机 + +明亮都市 + + + +▍角色类型库 · 专属记忆符号 + + + +隔辈守望者 +E. ELDER +白衬衫 · 老茧手 · 那句「不怕」 +替儿孙扛住整个时代的苦 +在场即守护,无言胜千言 + + + +逆袭反哺者 +R. REPAYER +亏欠→兑现:硬板床→软床 +把曾经没得到的,悄悄还回去 +反哺路上,每一步都是心疼 + + + +苦难中的母亲 +M. SUFFERING +红碎花棉袄 · 玉镯与粗手并存 +一辈子没喊过苦的母亲最苦 +沉默里,把全家扛在肩上 + + + +大爱原型人物 +A. ARCHETYPE +破三轮 · 起皱零钞 · 半个馒头 +他没想过自己,谁都想过 +朴素到极致,是最贵的善良 + + + +贵人引路人 +G. GUIDE +高马尾 · 黑外套 · 指路手势 +一句话点醒,一辈子不忘 +指路的手势,比讲理更动人 + + + +倔强追梦者 +D. DREAMER +不服老 · 执拗 · 圆梦 +世界觉得他不行,他偏要行 +最倔的人,心里藏着最柔软 + + + +▍六段式情绪核弹结构 + + +开场钩子 + +绝境施压 + +转机拉扯 + +核爆段 + +闭环升华 + +落版段 + +破壁 +3-7秒 揪心 +20% 谷底 +25% 拉锯 +25% 撕心裂肺 +15-20% 留白 +10-15% 落版 +破壁在核爆后 +情绪节奏总览:压抑蓄力 → 核爆释放 → 破壁落地,每 10-30 秒完成一次情绪反转 + + + +▍红线清单 · 绝不能做的事 + + +×消费苦难:不消费,只让观众看见人 + +×伪造真实素材:破壁史料必须可溯源 + +×美化悲剧:不加滤镜不配哀伤煽情 + +×角色没有名字:有名字才是一个人 + +×强行好结局:不否定真实的痛苦 + +×植入打断情绪:核爆/破壁段不插广告 + + + +数据来源:2026-08-26 企微 · 群像账号 · TOP6 数据看板 +旧梦留声机 · 账号设定卡 · content requirement board + diff --git a/project/短视频脚本创作/V1.0/subskill/mcn-script-review/Creation-Review-Method/README.md b/project/短视频脚本创作/V1.0/subskill/mcn-script-review/Creation-Review-Method/README.md new file mode 100644 index 0000000..fe8880a --- /dev/null +++ b/project/短视频脚本创作/V1.0/subskill/mcn-script-review/Creation-Review-Method/README.md @@ -0,0 +1,198 @@ +# Creation Review Method + +> Method: Creation Review | 创作复盘方法 +> Status: 有效 (2026-08-22) +> Scope: 短视频脚本创作全流程(S1-S11)问题复盘与经验沉淀 +> 本目录存放创作流程中出现的问题复盘方法与经验总结,供后续创作/改写时参考,避免同类问题复现。 + +--- + +## 复盘方法总览 + +创作出现问题 → 按以下三层归因 → 定位环节 → 修改对应步骤 → 沉淀经验。 + +### 第一步:问题归因(三层判定) + +| 层 | 判定问题 | 关键问 | +|----|---------|--------| +| **事件层** | 事件设计本身是否成立 | 这个事件放在任何位置都立得住吗?还是本身就有硬伤? | +| **手法层** | 叙事手法使用是否得当 | 手法本身没问题,是选择或执行错了? | +| **流程层** | 流程是否有把关环节 | 是 AI 执行偏差,还是流程设计本身缺这道闸? | + +> 归因优先级:先查事件层(设计硬伤),再查手法层(表现方式),最后查流程层(缺把关)。 + +### 第二步:定位环节(S5→S11) + +| 环节 | 负责什么 | 问题迹象 | +|------|---------|---------| +| S5 选题 | 故事前提声明 | 秘密/信息边界从未被定义 | +| S6 框架 | 事件设计 + 复盘(主闸) | 事件破坏前提、动机缺失、高潮前置违规 | +| S7 大纲 | 事件展开(继承 S6 标注) | 展开时改变前提关系、升级烈度 | +| S9 脚本 | 开场演绎(按 S6 已定结构) | 自行决定是否前置、更换开场事件 | +| S11 诊断 | 专项兜底(前提/因果/动机) | 脚本成型后仍带逻辑硬伤 | + +### 第三步:修改对应环节 + +- 前提问题 → 回 S6 重排事件/曝光节点 +- 动机问题 → 回 S6/S7 补动机铺垫或调低烈度 +- 开场问题 → 回 S6 重做开场决策(S9 只演绎不决策) + +--- + +## 经验沉淀 + +### 经验 1:事件设计必须通过「故事成立性三层校验」 + +> 来源:2026-08-22 当众撕包揭穿假快递案例 + +**问题**:改写脚本开场 = 新邮递员当众撕包裹示众,喊出「全是假的!寄件人是你自己写的!」。后续场5村民又演「原来如此」——逻辑崩了。 + +**三层校验判定**: +- 前提一致性 ❌:秘密被公开拆穿 → 十年守护前提崩塌;场5认知倒挂 +- 因果链条 ❌:无过渡直接进入当众撕包 +- 动机合理性 ❌:小周与罗爷爷无仇无怨,行为烈度远超动机,零铺垫 + +**经验要点**: +1. **冲突强 ≠ 事件成立**:一个事件冲突再强,如果破坏故事核心设定(秘密/规则),放在任何位置都立不住 +2. **事件与前提校验**:设计每个事件时问「这件事发生之后,故事还能按原设定走下去吗?」 +3. **动机与烈度匹配**:行为烈度 ≤ 动机强度;无仇无怨的角色不能做无理由的激烈行为 +4. **正确做法参考**(原视频):知情者私下质问「你啷个还在骗这些娃娃哦」——秘密保留、动机成立、烈度匹配 + +### 经验 2:高潮前置倒叙——前置的是「结果切片」,不是完整高潮桥段 + +> 来源:同案例的手法层归因 + +**问题**:AI 把「当众撕包揭穿」(全片最大冲突)前置到开场,导致: +- 剧透全部(秘密当场公开) +- 情绪峰值提前耗尽(后续蓄力→释放闭环断裂,观众没有理由看下去) + +**经验要点**: +1. **前置的是悬念,不是情绪终点**:前置片段让观众产生"怎么发生的/为什么"的好奇(勾起兴趣),不是让观众"已经知道结局/情绪已走完" +2. **可前置的高光类型不限**:冲突/情感/治愈瞬间都可以,但必须是"结果感+未解之谜"的切片 +3. **信息量收敛**:≤1-2秒快闪 + 时间标记(如"十年前"),不演完整桥段/对白/示众 +4. **拆穿秘密类事件禁止前置**:「当众揭发/拆穿秘密」类事件前置 = 剧透全部 + 破坏前提 +5. **开场方式选择不设类型禁区**:悬念/冲突/共鸣/反差/高光/提问都行,凡能引起观看兴趣即可——约束只落在「高潮前置倒叙」这一种手法的使用规则上 + +### 经验 3:判断问题归属——先事件层,再手法层,最后流程层 + +> 来源:案例复盘过程中的方法纠偏 + +**纠偏过程**: +- 初判:把问题归因于「高潮前置倒叙」手法 → 错(手法是表现层,事件本身才是硬伤) +- 修正:问题在「当众撕包揭穿」这个**事件本身**——即使不前置、放在任何位置都会泄密 + +**经验要点**: +1. **事件设计是根本**:坏事件放哪都是坏的;手法只是放大或收敛它的影响 +2. **不要把手法问题当事件问题,也不要把事件问题当手法问题**:先确认事件本身成立,再谈表现方式 +3. **流程把关闭环**:S6 是主闸(事件设计时拦截),S11 是兜底(成型后复核)——中间环节(S7/S9)不重复设检查,只执行 S6 已把关的决策 + +### 经验 4:职责分离——结构决策在 S6,演绎执行在 S9 + +> 来源:是否前置/如何前置的归属讨论 + +**结论**: +- **是否前置、前置什么事件、信息是否收敛 → S6 决策**(场次结构表 + 叙事逻辑与动机复盘) +- **如何演绎前置片段(画面/台词/节奏)→ S9 执行**(按 S6 已定开场场次) + +**理由**: +1. 倒叙开场改变场次结构的时间线 → 属于结构决策,S6 产出场次结构表 +2. S6 的事件-前提校验天然覆盖「前置是否泄密」 +3. S9 只负责把 S6 定的开场演出来,不得自行决定是否前置/更换开场事件 + +### 经验 5:角色对话信息合法性——「知道的」才能说出口 + +> 来源:2026-08-22 新邮递员断言「造假十年」案例(脚本 #23 二次复盘) + +**问题**:场3 老赵退休才把钥匙交给「新来的邮递员小刘」(刚接手),场4 小刘却摔箱断言「十年!你造假造了十年!」——「十年」是只有老赵/村民(长期在场者)才知道的时间跨度,新角色说出即**信息越界**。 + +**归因**:流程层缺口(S6 复盘只查「行为符合身份」,不查「对话信息符合角色认知边界」)+ 台词瑕疵。 + +**校验要点**: +1. **角色的每句关键断言,必须在其认知边界内**(身份×经历×出场时间)——他只能说他能知道的(亲眼所见/亲历/合理推断/他人告知,推断链条要有依据) +2. **身份×经历×出场时间 = 认知边界**:新来的→只知道接手后看到的;长期在场→才知道历史;知情者→才知道秘密 +3. **数量 ≠ 年限**:「退回件堆了一柜子」只能推断「有假件」,不能推断「造假十年」——断言超过证据范围即越界 +4. **与「行为动机」区分**:动机查「为什么做」,信息合法性查「凭什么知道」——两者都要过 +5. **修复示范**:新邮递员应说「退回的件堆了一柜子!你造假快递,不是一天两天了吧」(可推断),而非「十年!你造假造了十年!」(不可知) + +### 经验 6:冲突强度来自动机与台词,不是摔得狠、吼得响、围观多 + +> 来源:2026-08-22 脚本 #23 场1 揭穿段连发问题(摔箱/吼/围观议论) + +**问题**:场1 开场 = 新邮递员「狠狠摔箱 + 一句吼声砸下来 + 围观村民议论声潮水般涌上来」——三个过度表达叠在一起,但角色动机只是「职责发现退回件」(平和)。表现强度远超动机强度 → 观众感到刻意、假。 + +**归因**:共性根因——**AI 用「加烈度、加音量、加围观」堆冲突,但动机撑不起**。这是「动机与烈度匹配」(经验1)在**表达方式维度**的细化,原检查粒度太粗没拦住。 + +**校验要点**: +1. **冲突强度 = 动机分量 × 台词张力**,不是动作音量围观人群 +2. **动作烈度需暴力动机支撑**:摔/砸/撕配职责发现 = 不匹配;平和动机配平和动作(放/看/问) +3. **音量烈度**:私下发现的问题配私下质问(低声一字一顿),不当众吼 +4. **公开程度**:秘密型故事的开场揭穿应私下进行——「围观村民议论」暗示事件已公开、秘密已不存,与前提冲突;村民在场保留到高潮 +5. **台词张力替代音量**:安静地精准戳穿("寄件人查无此人")比吼更有压迫感 +6. **判别**:观众看完是"这冲突来得自然、有分量"还是"这角色怎么突然这么激动"——后者 = 表达方式过度 + +### 经验 7:把关点要收敛,不要摊饼 + +> 来源:2026-08-22 故事成立性校验方案设计过程 + +**问题**:设计「故事成立性」修复方案时,把同一套校验(前提/因果/动机)摊到 S5/S6/S7/S9/S11 五个环节,每个环节都加一点——**为"每环节都有产出"而摊饼**,导致改动面大、职责重叠、中间环节冗余。后被收敛为:S5 供基准 + S6 主闸 + S11 兜底,中间环节(S7/S9)只执行不重复设检查。 + +**经验要点**: +1. **把关点收敛到少数关键环节**:源头主闸(事件/结构设计处)+ 兜底(成型后复核),中间环节不重复设检查 +2. **判断信号**:改动触及 3+ 个环节时,停下来确认是否有单一主责环节可以承担 +3. **职责不重叠**:每个检查只在一个环节把关一次,避免 S5-S11 各查一遍同一件事 +4. **每环节有自己的职责**:S5 供基准、S6 定结构/把关、S7 继承展开、S9 演绎、S11 兜底——不是每处都放同一把尺子 + +### 经验 9:修表层更要修根因,避免挤牙膏式连环补 + +> 来源:2026-08-22 脚本 #23 连环问题(撕包→十年→摔吼围观) + +**问题**:同类问题三次连环暴露——第一次修「当众撕包揭穿」(表层:改台词),随后又暴露「十年信息越界」、再暴露「摔箱/吼/围观议论」。每次只修了**当前看到的表层问题**,没有一次性回溯深层共性根因,导致同类问题换个形式再来。 + +**经验要点**: +1. **发现问题先问「执行偏差还是流程缺口」**:表层改脚本台词只是执行偏差修复;若是流程缺口(缺把关),必须补流程闸,否则同类问题换形式复发 +2. **连环暴露的信号**:同一脚本/同一类问题第二次出现时,停止逐条补丁,回溯**共性根因**(本次共性:AI 用过度表达堆冲突、动机撑不起) +3. **一次性收敛**:定位到根因后,把该根因的所有表现形态一次性覆盖(动作烈度/音量/公开度/信息跨度),并沉淀为一条经验,避免第三次复发 +4. **沉淀闭环**:每次修复后问「这个问题下次还会以什么形式出现?」——答案即下一条要补的检查点 + +### 经验 10:冷开场切片与后文完整演绎——事件属性必须一致或明示递进(起疑级对白可留、揭穿级对白禁演) + +> 来源:2026-08-22 脚本 #23 复盘(场1 私下平静一问 vs 场4 当众摔箱揭穿) + +**问题**:冷开场(场1)与后文高潮(场4)疑似同一揭穿事件的两次演绎,但公开度/氛围/烈度不一致——场1 私下低声问「这包裹,是假的吧?」,场4 当众摔箱+吼+村民围拢。观众易产生「同一幕两种演法」的错位感;且场1「十年后」字幕与场3 十六岁时间线重叠,冷开场时间定位模糊。 + +**归因**:手法层(经验2「前置结果切片」规则细化不足)——经验2 只约束「快闪+不演完整桥段/对白」,未约束「切片与后文完整演绎的事件属性一致性」与「对白分级」。 + +**校验要点**: +1. **切片=预叙时,事件属性必须与后文一致**:若冷开场是高潮事件的预叙切片,公开度(私下/当众)、围观状态、动作烈度须与后文完整演绎对得上,否则观众错位 +2. **切片=独立事件时,必须明示递进**:若设计为「私下试探→公开爆发」的递进,需加时间/逻辑锚点(如切片里小刘手中一叠退回件),让观众一眼读懂两场不是重复 +3. **对白分级**:冷开场对白只允许「起疑级」(秘密的危机开端,如「这包裹,是假的吧?」),禁止「揭穿级」(秘密的总爆发,如「你造假十年!」)——后者必须保留到高潮 +4. **时间锚点**:冷开场的「十年前/十年后」字幕须与主线时间线自洽,重叠或矛盾时改用模糊标记(「很多年前」)或画面锚点定位 + +### 经验 11:剧情脚本混入纪实/素材段时,AI 标识与素材来源标注必须落实 + +> 来源:2026 脚本 #23《门卫爷爷的「假包裹」》复盘(AI写脚本 #23,场7 纪实素材段) + +**问题**:剧情段(AI 生成)与真实纪实素材混剪,旁白断言「这不是虚构。在我们看不到的山村里,这样的『假包裹』真实存在过」,但片内未做 AI 生成内容标识、未注明素材来源/情景示意——观众易把剧情素材当真实新闻,存在平台合规(虚假纪实/误导)风险。 + +**归因**:流程层缺口——S11 诊断只查故事逻辑/合规词,不查「素材来源与 AI 标识」。 + +**校验要点**: +1. **混剪必标注**:剧情类脚本混入纪实/新闻素材,必须在字幕注明素材来源,或标注「素材示意/情景再现」,不得让观众误判为真实新闻 +2. **AI 生成内容显著标识**:按平台规则(抖音等 AI 生成内容标识要求),AI 生成段落需显著标识 +3. **「真实存在」断言需有依据**:旁白「这不是虚构/真实存在过」需有事实支撑,或降级为软性表达(「据说/在我们看不到的山村」) +4. **S11 兜底增加检查项**:素材来源、AI 标识、真实性断言三查,作为剧情类脚本的合规兜底 + +--- + +## 复盘流程(可执行步骤) + +``` +发现问题(逻辑/情绪/动机/传播问题) + → 1. 归因三层:事件层?手法层?流程层? + → 2. 定位环节:S5 前提声明?S6 事件/复盘?S7 展开?S9 演绎?S11 兜底? + → 3. 判断是「执行偏差」还是「流程缺口」 + · 执行偏差 → 修改对应步骤产物,重跑该环节 + · 流程缺口 → 修改技能流程(S5/S6/S7/S9/S11 或知识库),补把关点 + → 4. 沉淀:把案例与经验写入本目录对应文件 + → 5. 验证:用新流程重跑同类场景,确认能拦截 +``` diff --git a/project/短视频脚本创作/V1.0/subskill/mcn-script-review/SKILL.md b/project/短视频脚本创作/V1.0/subskill/mcn-script-review/SKILL.md new file mode 100644 index 0000000..75c1757 --- /dev/null +++ b/project/短视频脚本创作/V1.0/subskill/mcn-script-review/SKILL.md @@ -0,0 +1,158 @@ +--- +name: 脚本复盘 +description: > + 短视频脚本复盘技能:只做分析与建议,不修改脚本。 + 【脚本质量复盘】对已有脚本做评分式质量复盘; + 【对比分析】对比 AI 脚本与视频源脚本的差异; + 【创作过程复盘】复盘出问题的创作过程,沉淀经验避免再犯。 + 触发场景:复盘脚本、点评脚本、分析脚本问题、对比 AI脚本与源脚本、 + 复盘某次创作过程、总结经验避免再犯。 + 适用平台:抖音/快手/视频号/B站/小红书。 +version: "1.2" +updated_at: "2026-08-22" +last_change: "description 精简为基础要求(功能全量实现在正文/references,调用 prompt 不重复实现细节)" +agent_created: true +--- + +# 脚本复盘 Skill + +## 技能概述 + +本技能提供脚本复盘的完整能力,与创作技能(short-video-script)解耦: + +- **脚本质量复盘**:对已有脚本做质量点评(复用 short-video-script 的判定标准,只读引用) +- **创作过程复盘**:对出问题的创作过程做三层归因与经验沉淀 +- **经验沉淀**:新问题/新经验写入 `Creation-Review-Method/README.md` 经验库 + +> ⚠️ **核心约束**:本技能只做**分析与建议**,不修改脚本、不触发创作流程、不改 short-video-script 的创作步骤文件。 + +--- + +## 触发条件 + +当用户表达以下任一意图时加载本技能: + +- 📝 **脚本复盘**:对已有脚本做质量复盘/点评("复盘这个脚本""点评一下""看看这脚本有什么问题")→ 独立分析模式 +- 📊 **对比分析**:对比 AI脚本 和 源脚本 的差异("对比 AI脚本和源脚本""分析两者差异")→ 对比分析模式 +- 📋 **创作复盘**:对某次创作过程做归因复盘("复盘为什么会出现这个问题""总结经验避免再犯") +- 💡 **经验沉淀**:把复盘发现的新问题沉淀为经验("把这个教训记下来") + +> 用户要求「对比/差异分析」→ 对比分析模式;否则默认独立分析模式(评分制)。 + +--- + +## 脚本质量复盘流程(独立分析模式 · 评分制) + +对「已有脚本」(如 AI写作复盘 页保存的脚本)做质量复盘,**每维 100 分制评分,总分等权平均**: + +1. **读取脚本**:从用户提供处读取脚本正文(文本/文件/数据库记录) +2. **判定内容类型**(防误用,必做):剧情/悬念/反转类 → 教程/干货类 → 口播/观点/测评类——决定哪些检查项适用(剧情类查故事成立性、教程类查教程完整链,互不误用) +2.5 **逻辑硬伤预检(剧情类评分前必做)**:通读全文,对照**逻辑硬伤判定清单**,命中即在问题清单记「高」,并按数量决定叙事逻辑维分数上限: + - **判定清单**(任一命中 = 逻辑硬伤 = 严重度「高」): + · 时间线/年龄账矛盾(前后数字、岁数、年份对不上) + · 动机铺垫不足 / 为反转而反转(事件为道具/转折硬设,无前文推导) + · 行为来源穿帮(道具/资源来路不合理,行为无由头) + · 逻辑范围失当(范围扩张/收缩无过渡) + · 前后事实/信息不符(同一对象状态、位置前后矛盾) + - **判定示例(对照用,防边界模糊)**: + · "角色夜里意外受伤入院,引出此前毫无铺垫的关键道具" → 为反转而反转/因果跳跃 → **高**(即使可加伏笔补救,仍是硬伤) + · "善意行为的作用范围从个体扩到群体,无过渡交代" → 范围失当 → **高** + · "角色年龄与工作年限对不上" → 年龄账矛盾 → **高** + · "台词含绝对化词'最'" → 合规瑕疵(非逻辑)→ **低** + · "场1/场4信息重复" → 表达细节(非逻辑)→ **低** + - **叙事逻辑维分数上限(按命中数)**:1 个 → ≤60;2 个 → ≤40;3 个及以上 → ≤20 +3. **表格维度评分**(与 AI写作复盘 列表对齐,6 维,每维 X/100): + - 设定承接:记忆符号/行为决策/内容规则体现 + - 框架节奏:逻辑链条/爆点贯穿/钩子密度/信息密度/节奏变化/结尾收口 + - 钩子设置:开场钩子/中间留人/中段不疲/结尾收口(全片留人链路) + - 叙事逻辑:前提/因果/动机/信息/表达方式/逻辑链条(仅剧情类强制);**受 2.5 预检分数上限约束** + - 极致事件:爆点贯穿/动作描述/场景/可拍性 + - 共鸣和共情:类型命中/具象场景/自嗨检测/3问自检 +4. **追加维度评分**(表格外,4 维,每维 X/100): + - 格式规范性:画面描述/景别/时长 + - 台词口语化:书面词/长句/语气词/重复词/人设一致性 + - 内容合规:绝对化/承诺/数据来源/竞品/违禁词 + - 教程完整链:仅教程类(成品→材料→步骤→收尾) +5. **总分统计**(100 分制):**总分 = (Σ其他适用维得分 + 叙事逻辑×2) / (适用维数 + 1)**(**叙事逻辑权重 2 倍**——故事逻辑是剧情类立身之本;不适用维度标记 null/N-A 不计入分母);**叙事逻辑 <80 时,总分不超过叙事逻辑分**(逻辑是根本,总分不能高于逻辑分);判定 ≥75 可交付 / 60-74 需优化 / <60 需大改 +6. **输出复盘报告**: + - 各维得分 + 档位(90-100 优秀 / 75-89 良好 / 60-74 及格 / 40-59 有问题 / 0-39 严重) + - 总分 + 综合判定 + - 问题清单(按严重度 🔴高/🟡中/🟢低)+ 定位 + 修改建议 + 所属环节(S5-S11) + - ⚠️ **严重度强制规则(故事逻辑是根本,命中即「高」,不得降级)**: + · **逻辑硬伤判定清单**(任一命中 → 严重度「高」):时间线/年龄账矛盾、动机铺垫不足、行为来源穿帮、逻辑范围失当(扩张/收缩无过渡)、为反转而反转/因果跳跃、前后数字/事实不符 + · **叙事逻辑维按硬伤数量梯度扣分**:1 个硬伤 → 该维 ≤60;2 个 → ≤40;3 个及以上 → ≤20 + · **判定边界(禁止降级)**:不要把逻辑问题当"表达细节/可改小瑕疵"标低——只要属清单内(哪怕可改),一律「高」;只有合规瑕疵、表达细节(非逻辑)才可标「低」 +7. **沉淀**:复盘中发现**新类型问题**(经验库未覆盖)→ 按「经验沉淀」写入经验库 + +> 判定标准来源(跨技能只读引用,不复制): +> - `short-video-script/references/知识库/09_脚本诊断/脚本质量自动检查清单_八类35项.md` +> - `short-video-script/references/知识库/09_脚本诊断/故事成立性校验_通用_逻辑检查.md` +> - `short-video-script/references/创作流程/11_脚本检查和诊断.md`(诊断方法参考) + +--- + +## 对比分析流程(三段式:AI 脚本 → 源脚本 → 对比) + +对「AI脚本 vs 视频源脚本」做对比复盘(详细见 `references/脚本复盘流程.md` 三、对比分析模式): + +**第 1 段:AI 脚本独立分析** +- 数据:`rewrite_log.script_text` + `rewrite_log.topic`(AI 选题) +- 10 维逐维评分(同独立分析模式) + +**第 2 段:源脚本独立分析** +- 数据:`account_video_source.content_json` + - 优先:content 中「# 视频脚本」+「# 选题」(新格式) + - 回退:`account_videos.topic`(源选题)+ 分镜头表(旧格式) + - 关联:rewrite_id → video_id → account_videos.id +- 10 维逐维评分(同独立分析模式) + +**第 3 段:对比(中立评估)** +- 逐维对比表:**相同的点 / AI 好的点 / AI 差的点 / 中立结论(AI优于/劣于/持平源)** +- 总分对比:AI X/100 vs 源 Y/100 +- 综合判断:AI 整体优于/劣于/各有优劣 + 建议(保留优势、补回被削弱的) + +**中立原则**:不预设立场——客观列出差异,每维给依据。 + +--- + +## 创作过程复盘流程 + +对出问题的创作过程做归因复盘(方法见 `Creation-Review-Method/README.md`): + +``` +发现问题(逻辑/情绪/动机/传播问题) + → 1. 归因三层:事件层?手法层?流程层? + → 2. 定位环节:S5 前提声明?S6 事件/复盘?S7 展开?S9 演绎?S11 兜底? + → 3. 判断是「执行偏差」还是「流程缺口」 + · 执行偏差 → 修改对应步骤产物,重跑该环节 + · 流程缺口 → 若需改创作技能,先列改动清单等授权(方案与执行分离) + → 4. 沉淀:新问题/新经验写入 Creation-Review-Method/README.md + → 5. 验证:用新流程重跑同类场景,确认能拦截 +``` + +--- + +## 经验沉淀 + +- 复盘中发现**经验库未覆盖**的新问题类型 → 按经验格式(来源/问题/归因/校验要点)追加到 `Creation-Review-Method/README.md` +- 经验编号顺延(当前至经验 10),保持「创作内容/流程/分析」类经验在 Creation-Review-Method + +--- + +## 目录结构 + +``` +script-review/ ← Skill 根目录 +├── SKILL.md ← 入口(触发条件 + 复盘流程 + 沉淀规则) +├── Creation-Review-Method/ +│ └── README.md ← 创作过程复盘方法 + 经验库(三层归因 + 9 条经验 + 复盘流程) +└── references/ + └── 脚本复盘流程.md ← 已有脚本质量复盘流程(判定标准跨技能引用) +``` + +## 与相关技能的关系 + +| 技能 | 职责 | 协作 | +|------|------|------| +| short-video-script | 脚本**创作** + 创作流程内 S11 诊断 | 本技能只读引用其判定标准 | +| **script-review(本技能)** | 已有脚本**复盘** + 创作过程**复盘** + 经验沉淀 | 独立运行,不侵入创作流程 | diff --git a/project/短视频脚本创作/V1.0/subskill/mcn-script-review/references/脚本复盘流程.md b/project/短视频脚本创作/V1.0/subskill/mcn-script-review/references/脚本复盘流程.md new file mode 100644 index 0000000..7918e71 --- /dev/null +++ b/project/短视频脚本创作/V1.0/subskill/mcn-script-review/references/脚本复盘流程.md @@ -0,0 +1,295 @@ +# 脚本复盘流程(已有脚本质量复盘) + +> 对「已有脚本」做质量复盘——独立于创作流程(S1-S11),只分析不修改。 +> 判定标准**跨技能只读引用** short-video-script 的 `references/知识库/09_脚本诊断/`,不复制内容,保证与创作技能诊断口径一致。 + +--- + +## 〇、两种执行模式(按需求选择) + +| 模式 | 适用需求 | 输出 | +|------|---------|------| +| **独立分析** | 「复盘这个脚本」「检查脚本质量」「给脚本打分」 | 每维 100 分制评分 + 总分(10 维等权平均) | +| **对比分析** | 「对比 AI脚本 和 源脚本」「分析两者差异」 | 三段式(AI→源→对比),逐维「相同的点/好点/差点」差异评析 | + +> 用户要求「对比/差异分析」→ 对比分析模式;否则默认独立分析模式。 + +--- + +## 一、适用范围 + +| 场景 | 说明 | +|------|------| +| 复盘已有脚本 | 任意已保存的脚本(如 AI写作复盘 页的脚本 #23、历史改写记录等) | +| 点评脚本 | 用户给出脚本文本/文件,要求质量点评(独立分析) | +| 对比分析 | 同一选题多版脚本 / AI脚本 vs 源脚本 对比(对比分析) | + +**不适用**:创作流程内的 S11 诊断(那是创作最后一步,对刚生成的脚本做质检,属 short-video-script 职责)。 + +--- + +## 二、独立分析模式(评分制) + +### 1. 读取脚本 +从用户提供处读取:脚本文本 / 文件路径 / 数据库记录(如 mcn-plugin.db 的 rewrite_log)。 + +### 2. 逐维评分(每维 100 分制,总分等权平均) + +> 判定标准**只读引用** `short-video-script/references/知识库/09_脚本诊断/`(八类35项 + 故事成立性校验),逐维检查并评分。 +> 维度顺序与 AI写作复盘 表格列对齐(设定承接/框架节奏/钩子设置/叙事逻辑/极致事件/共鸣和共情),表格外维度追加在后。 + +#### 评分锚点(每维 100 分制) + +| 分数段 | 档位 | 含义 | +|--------|------|------| +| 90-100 | 优秀 | 接近爆款水准(满分极少,需有可对标爆款案例支撑) | +| 75-89 | 良好 | 达到行业平均水平 | +| 60-74 | 及格 | 基本可用但不出彩 | +| 40-59 | 有明显问题 | 影响传播效果,需修改 | +| 0-39 | 严重缺陷 | 不可用,必须大改 | + +> 评分不是「有没有问题」而是「多接近爆款水准」——高分需证明「好在哪里」,低分需指出「差在哪里」。 + +#### 总分(叙事逻辑 2 倍权重) + +``` +总分 = (Σ其他适用维得分 + 叙事逻辑×2) / (适用维数 + 1) +说明:叙事逻辑权重 2 倍(故事逻辑是剧情类立身之本,出问题总分受重大影响); + 不适用维度标记 null/N-A,不计入分母(如剧情类的教程完整链) +判定:≥75 可交付;60-74 需优化;<60 需大改 +``` + +#### 2.1 先判内容类型(防误用,必做) + +> ⚠️ **复盘前必须先判定脚本的内容类型**,再按「适用类型」决定检查哪些项——避免把剧情脚本当教程检查(如"教人发假快递")、或把教程脚本当剧情查故事成立性。 + +| 内容类型 | 判定依据 | 检查范围 | +|---------|---------|---------| +| **剧情/悬念/反转类** | 有角色/故事线/秘密/反转/倒叙(如假快递守护、情感短剧) | 表格6维 + 格式/台词/合规 + **故事成立性(强制)**;教程完整链不适用 | +| **教程/干货类** | 教人做事:成品前置→步骤→收尾(如做菜、教学) | 表格6维 + 格式/台词/合规 + **教程完整链(强制)**;故事成立性不适用 | +| **口播/观点/测评类** | 口播表达/观点输出/产品测评,无故事线 | 表格6维 + 格式/台词/合规;教程完整链、故事成立性均不适用 | + +**判定错误的风险**:把剧情脚本套教程链检查 → 荒谬误判("教人发假快递");把教程脚本套故事成立性 → 错误打回。判定后按上表执行,不越界检查。 + +--- + +### 表格维度(6 项,与 AI写作复盘 列表对齐) + +#### 维度1:设定承接 + +| 检查项 | 判断标准 | 判定 | +|--------|---------|:---:| +| 设定承接 | 人设卡记忆符号/行为决策/内容规则在脚本中有体现 → 缺关键机制=不过 | 通过/不过 | + +#### 维度2:框架节奏 + +| 检查项 | 判断标准 | 判定 | +|--------|---------|:---:| +| 逻辑链条 | 前后因果关系清晰,无明显跳跃 → 逻辑混乱=不过 | 通过/不过 | +| 爆点贯穿 | 全片至少2次爆点(开头+至少1次中后段)→ 仅开头有=不过 | 通过/不过 | +| 钩子密度 | 每15秒至少1个小钩子/转折点 → 平铺直叙段=不过 | 通过/不过 | +| 信息密度 | 每10秒有新的有效信息 → 同信息重复=不过 | 通过/不过 | +| 节奏变化 | 有快慢交替(短句轰炸↔长句铺陈)→ 全程一个节奏=提醒 | 提醒 | +| 结尾收口 | 有明确的总结/话题/互动引导 → 戛然而止=不过 | 通过/不过 | + +#### 维度3:钩子设置(全片留人链路) + +> 不只开场钩子——覆盖「开场抓人 → 中间留人 → 结尾收口」全片钩子体系。 + +| 检查项 | 判断标准 | 判定 | +|--------|---------|:---:| +| 开场钩子 | 前3秒有画面+动作+钩子 → 空泛叙述=不过 | 通过/不过 | +| 中间留人钩子 | 每15秒至少1个小钩子/转折点 → 平铺直叙段=不过 | 通过/不过 | +| 中段不疲 | 30-50秒持续有新信息注入 → 中段重复前段内容=不过 | 通过/不过 | +| 结尾收口 | 有明确的总结/话题/互动引导 → 戛然而止=不过 | 通过/不过 | + +#### 维度4:叙事逻辑 + +> 命中**人物行为/故事逻辑硬伤**(行为来源穿帮如"随身携带体积不符的物件"、动机断裂、时间线/年龄账矛盾、逻辑范围失当、为反转而反转/因果跳跃)→ 严重度**「高」**(逻辑有问题其他维度不必再谈),**叙事逻辑维按硬伤数量梯度扣分**:1 个 → ≤60、2 个 → ≤40、3 个及以上 → ≤20——此类问题直接击穿故事成立性,不按普通瑕疵扣分。 + +| 检查项 | 判断标准 | 判定 | +|--------|---------|:---:| +| 前提一致性 | 事件是否破坏核心秘密/设定?有无认知倒挂? | 通过/不过 | +| 因果链条 | 事件可否由前文推导?有无跳跃? | 通过/不过 | +| 动机合理性 | 行为有无动机铺垫?烈度是否匹配? | 通过/不过 | +| **行为来源与时间线** | 行为道具/资源从何而来(携带/使用是否违背常理)?为何"此刻"行动、之前为何不做?临时起意 vs 预先准备是否矛盾? | 通过/不过 | +| 信息合法性 | 角色说的每句关键断言是否在其认知边界内? | 通过/不过 | +| 表达方式匹配 | 冲突强度来自动机与台词,还是堆砌摔/吼/围观? | 通过/不过 | +| 逻辑链条 | 前后因果关系清晰,无明显跳跃 → 逻辑混乱=不过 | 通过/不过 | + +#### 维度5:极致事件 + +| 检查项 | 判断标准 | 判定 | +|--------|---------|:---:| +| 爆点贯穿 | 全片至少2次爆点(开头+至少1次中后段)→ 仅开头有=不过 | 通过/不过 | +| 动作描述 | 镜头描述含具体动词(走/拿/放/指/举/翻)→ 没有=不过 | 通过/不过 | +| 场景描述 | 有具体场景/道具/环境 → 抽象场景=提醒 | 提醒 | +| 不可拍警告 | 含"感觉/氛围/意境/仿佛"等抽象词 → 标记提醒 | 提醒 | + +#### 维度6:共鸣和共情 + +| 检查项 | 判断标准 | 判定 | +|--------|---------|:---:| +| 共鸣类型命中 | 至少命中6种共鸣类型中的1种(经历/情绪/观点/身份/痛点/反差)→ 无法判断=不过 | 通过/不过 | +| 具象场景检验 | 至少有1个"零解释秒懂"的具象场景(时间/地点/动作/道具)→ 全是大道理=不过 | 通过/不过 | +| 自嗨检测 | 把"我"换成目标人群角色(打工人/宝妈/学生)仍成立 → 只有创作者成立=不过 | 通过/不过 | +| 3问自检 | 观众能联想自己经历 + 会评论"我也是" + 不全是空洞大道理 → 3问全不过=不过 | 通过/不过 | + +--- + +### 追加维度(表格外,补充检查) + +#### 维度7:格式规范性 + +| 检查项 | 判断标准 | 判定 | +|--------|---------|:---:| +| 画面描述 | 每个镜头有具体动作/场景 → 纯文字叙述=不过 | 通过/不过 | +| 景别标注 | 关键镜头有景别(特写/近景/中景/远景)→ 缺失=提醒 | 提醒 | +| 时长标注 | 每个镜头段有时长说明 → 缺失=提醒 | 提醒 | + +#### 维度8:台词口语化 + +| 检查项 | 判断标准 | 判定 | +|--------|---------|:---:| +| 书面词汇 | 无"此外/综上所述/由此可见"等书面连接词 → 出现=不过 | 通过/不过 | +| 长句检测 | 单句不超过30字 → 超过=不过 | 通过/不过 | +| 自然语气词 | 有口语化语气词(呢/吧/啊/哦/嘛)→ 完全没有=不过 | 通过/不过 | +| 重复用词 | 同一非关键词3秒内不重复 → 重复=提醒 | 提醒 | +| 人设一致性 | 台词逐句对表人设卡(说话风格/口头禅/行为决策)→ 明显不符=不过 | 通过/不过 | + +#### 维度9:内容合规 + +| 检查项 | 判断标准 | 判定 | +|--------|---------|:---:| +| 绝对化词汇 | 命中"第一/最好/唯一/极致/顶级/最X"=不过 | 直接不过 | +| 绝对效果承诺 | 命中"绝对有效/100%/立竿见影/保证/根治"=不过 | 直接不过 | +| 无来源数据 | "提升X倍/效果翻倍"无来源标注=不过 | 直接不过 | +| 竞品名称 | 含竞品品牌名/产品名=警告 | 警告 | +| 暗示贬低 | 含"比某些/比别的/比市面上"=警告 | 警告 | +| 行业违禁词 | 医美(消除/根治/永久/祛除)/美妆(祛斑/消炎/杀菌/抗皱)/食品(治疗/治愈/预防疾病)=不过 | 直接不过 | + +#### 维度10:教程完整链(**仅教程/干货类适用**) + +| 检查项 | 判断标准 | 判定 | +|--------|---------|:---:| +| 教程完整链 | 成品/效果前置→材料清单→逐步操作(≥3步,每步含关键技巧)→成品对比收尾;剧情/口播/测评类=不适用,跳过 | 通过/不过 | + +--- + +### 总分(最后统计,100 分制) + +| 汇总 | 计算 | +|------|------| +| 各维得分 | 10 维各得 X/100 分 | +| **总分** | **Σ(10 维得分) / 10**(等权平均) | +| 综合判定 | ≥75 可交付;60-74 需优化;<60 需大改 | + +> 每维评分参考判定锚点(90-100 优秀 / 75-89 良好 / 60-74 及格 / 40-59 有问题 / 0-39 严重缺陷)。 + +### 4. 输出复盘报告 + +``` +## 脚本复盘报告(独立分析) +- 脚本:标题 / 来源(id 或文件) +- 内容类型判定:剧情/教程/口播测评(决定检查范围) +- 表格维度评分(与 AI写作复盘 列对齐,每维 100 分制): + | 维度 | 检查项 | 得分 | 档位 | 问题定位 | + |------|--------|:---:|:---:|---------| + | 设定承接 | 记忆符号/行为决策/内容规则体现 | X/100 | 优秀/良好/及格/有问题/严重 | … | + | 框架节奏 | 逻辑链条/爆点贯穿/钩子密度/信息密度/节奏变化/收口 | X/100 | … | … | + | 钩子设置 | 开场钩子/中间留人/中段不疲/结尾收口 | X/100 | … | … | + | 叙事逻辑 | 前提/因果/动机/信息/表达方式/逻辑链条 | X/100 | …(剧情类) | … | + | 极致事件 | 爆点贯穿/动作描述/场景/可拍性 | X/100 | … | … | + | 共鸣和共情 | 类型命中/具象场景/自嗨检测/3问自检 | X/100 | … | … | +- 追加维度评分(表格外,每维 100 分制): + | 维度 | 检查项 | 得分 | 档位 | 问题定位 | + |------|--------|:---:|:---:|---------| + | 格式规范性 | 画面描述/景别/时长 | X/100 | … | … | + | 台词口语化 | 书面词/长句/语气词/重复词/人设一致性 | X/100 | … | … | + | 内容合规 | 绝对化/承诺/数据来源/竞品/违禁词 | X/100 | … | … | + | 教程完整链 | 仅教程类:成品→材料→步骤→收尾 | X/100 | …(教程类) | … | +- 总分统计: + | 汇总 | 结果 | + |------|------| + | 各维得分 | 设定承接 X、框架节奏 X、…、教程链 X | + | **总分** | **Σ/10 = X/100**(等权平均) | + | 综合判定 | ≥75 可交付 / 60-74 需优化 / <60 需大改 | +- 问题清单(按严重度): + | # | 严重度 | 定位 | 问题 | 修改建议 | 所属环节 | + |---|:---:|------|------|---------|---------| + | 1 | 🔴高 | 场X台词 | … | … | S6/S7/S9 | +- 总结论:总分 X/100,✅ 可交付 / ⚠️ 需优化 / ❌ 需大改 +``` + +### 5. 沉淀经验 +复盘发现**经验库未覆盖**的新问题类型 → 追加到 `Creation-Review-Method/README.md`(格式:来源/问题/归因/校验要点,编号顺延)。 + +--- + +## 三、对比分析模式(三段式:AI 脚本 → 源脚本 → 对比) + +> 适用:用户要求「对比 AI脚本 和 源脚本」「分析两者差异」「对比复盘」。 +> 先各边独立分析(中立基准),再对比差异——不预设 AI 好或源好,客观评析。 + +### 第 1 段:AI 脚本独立分析 + +**数据源**: +- `rewrite_log.script_text`(AI 故事脚本全文) +- `rewrite_log.topic`(AI 选题:赛道/受众/视频主题/开场钩子等) + +**执行**:按「独立分析模式」的 10 维逐维检查并评分(每维 100 分制),输出 AI 脚本评估。 + +### 第 2 段:源脚本独立分析 + +**数据源**(按优先级): +1. **优先**:`account_video_source.content_json` 的 content 中「# 视频脚本」章节(完整源故事脚本)+ 同 content 的「# 选题」(源选题)——**新格式** +2. **回退**:只有「# 分镜头表」时(旧格式)——源选题用 `account_videos.topic`(关联 rewrite.video_id → account_videos.id),故事内容用分镜头表 + +**数据关联链**: +``` +rewrite_id → video_id → account_videos.id + → account_videos.topic(源选题) + → account_video_source.content_json(源脚本:优先 # 视频脚本,回退分镜头表) +``` + +**执行**:同一套 10 维逐维检查并评分,输出源脚本评估。 + +### 第 3 段:对比(中立评估) + +**方法**:两段评估结果**逐维对比**,不重新评分——列差异,给中立结论。 + +| 维度 | AI 脚本 | 源脚本 | 相同的点 | AI 好的点 | AI 差的点 | 中立结论 | +|------|---------|--------|---------|----------|----------|---------| +| 设定承接 | X/100 | X/100 | … | … | … | AI优于/劣于/持平 | +| 框架节奏 | … | … | … | … | … | … | +| 钩子设置 | … | … | … | … | … | … | +| 叙事逻辑 | … | … | … | … | … | … | +| 极致事件 | … | … | … | … | … | … | +| 共鸣和共情 | … | … | … | … | … | … | +| 格式规范性 | … | … | … | … | … | … | +| 台词口语化 | … | … | … | … | … | … | +| 内容合规 | … | … | … | … | … | … | +| 教程完整链 | … | … | … | … | … | … | + +**对比要点**: +- **相同的点**:两脚本共有的设定/结构/钩子/情感点(继承自同一选题) +- **AI 好的点**:AI 相对源脚本改进/优化之处(改写的剧情、更强的钩子、更顺的情绪) +- **AI 差的点**:AI 相对源脚本退化/缺失之处(丢掉的爆点、变弱的共鸣、逻辑硬伤) +- **中立结论**:每维判「AI优于/劣于/持平源脚本」,并给依据——不预设立场 + +**对比总结论**: +``` +- 各维中立结论汇总:AI 优于 N 维、劣于 M 维、持平 K 维 +- 总分对比:AI 总分 X/100 vs 源总分 Y/100 +- 综合判断:AI 脚本整体 优于/劣于/与源脚本各有优劣 源脚本 +- 建议:保留 AI 优势维度、补回源脚本被削弱的部分 +``` + +--- + +## 四、行为约束 + +1. **只分析不修改**:复盘输出报告与建议,不直接改脚本正文、不改创作技能文件 +2. **跨技能只读**:引用 short-video-script 判定标准时只读取,不复制、不修改其文件 +3. **方案与执行分离**:用户要求"复盘/分析"→ 只输出报告;如需修改脚本/技能 → 先列改动清单等授权 +4. **不触发创作流程**:复盘不进入 S1-S11,不影响创作技能的任何步骤 diff --git a/project/短视频脚本创作/V1.0/subskill/mcn-video-prompt/SKILL.md b/project/短视频脚本创作/V1.0/subskill/mcn-video-prompt/SKILL.md index 0b9edc2..98e4270 100644 --- a/project/短视频脚本创作/V1.0/subskill/mcn-video-prompt/SKILL.md +++ b/project/短视频脚本创作/V1.0/subskill/mcn-video-prompt/SKILL.md @@ -8,8 +8,8 @@ description: > 角色卡、首帧图、运镜、景别、时间戳分镜、图生视频。 version: "1.0" agent_created: true -updated_at: "2026-08-26" -last_change: "产出结构定稿:①开发机与③用户环境产出根目录由 桌面 MCNSkill项目/分镜提示词/ 改为 桌面 MCNSkill项目/(账号层直挂:{账号名}/提示词{编号}/,去掉「分镜提示词」技能文件夹层,与脚本创作「脚本{编号}」同构);dsh 环境不变 工作区 D:\dshworkspace\创作任务\{账号名}\提示词{编号}\(references-add/路径配置.md 判定表+执行规则+解析规则+示例+变更记录,SKILL.md 声明+示例树)" +updated_at: "2026-08-31" +last_change: "提示词输出结构 4层→6层重排(用户定稿):①主体列表→②画面质感(视觉风格+摄影质感前置)→③色调光影→④主体设定(人物/场景/构图)→⑤镜头叙事→⑥负面提示词;原「设定」层拆为画面质感/色调光影/主体设定三层,视觉基调前置(模型对提示词开头权重最高);同步 3.2.2 输出格式+3.2.1 思考清单+6层×5类型适配表+Step5 校验②结构完整性+SKILL.md F4/工作流/自检⑧引用(分镜提示词制作流程.md 同步修订)" deployment: source created_at: "2026-08-20" --- @@ -202,7 +202,9 @@ F2 在 5 步工作流框架内执行,其中步骤 2-3 包含两轮用户交互 > **⚠️ 字符限制冲突说明:** seedance2.0-prompt-skill 实测平台接受上限 2000 字符;Seedance2.0-ShotDesign v2.0 官方规范建议 ≤500 字符(中文)/ ≤1000 词(英文),品质最优区间 60-100 词(中文约 120-200 字符)。**以 500 字符为品质基准,2000 字符为硬性上限。** **详细方法论:** -- 6步总流程+路径分流+4层结构(主体按类别分行+负面词放最后+镜头朝向规则,11字段降为内部思考清单)+时长速查:`references/制作流程/分镜提示词制作流程.md` +- 6步总流程+路径分流+6层结构(主体/画面质感/色调光影/主体设定/镜头叙事/负面词,视觉基调前置,11字段降为内部思考清单)+时长速查:`references/制作流程/分镜提示词制作流程.md` + +**详细方法论:** - 路径C双范式(反应/凝视)+11条核心原则+版权审查SOP:`references/制作流程/图生视频与分镜板制作流程.md` - 四维编码(Z景别/Y高度/X方位/F滤镜):`references/知识库/04_镜头类型/镜头景别词库.md` + `references/知识库/06_焦距视角/焦距视角词库.md` - 运镜安全写法+运动强度分级:`references/知识库/05_运镜方式/运镜方式词库.md` @@ -252,7 +254,7 @@ F2 在 5 步工作流框架内执行,其中步骤 2-3 包含两轮用户交互 **前置:剧本分析(Step 1,必做)** -进入路径分流前,先完成剧本类型判定(A叙事剧情/B单人Vlog/C旁白知识/D产品展示/E纯氛围)+ 维度拆解(①节奏全类型必做,②-⑤按类型选做),产出驱动后续路径选择、景别设计、微表情弧线、台词语气修饰、4层格式适配。详见 `references/制作流程/分镜提示词制作流程.md` Step 1。 +进入路径分流前,先完成剧本类型判定(A叙事剧情/B单人Vlog/C旁白知识/D产品展示/E纯氛围)+ 维度拆解(①节奏全类型必做,②-⑤按类型选做),产出驱动后续路径选择、景别设计、微表情弧线、台词语气修饰、6层格式适配。详见 `references/制作流程/分镜提示词制作流程.md` Step 1。 **处理流程(按路径分流):** @@ -270,7 +272,7 @@ F4 输出 = 主题 + 导演阐述 + 完整提示词(代码块包裹)。多 **单段(路径 A,≤15s):** 主题 + 导演阐述 + 单块代码块提示词 -提示词内部结构(4层结构,按类型适配):`①主体列表(置顶,按类别分行:人物/场景/道具,@图片引用优先)→ ②设定(人物逐人必写不可遗漏/场景/光线/构图/视觉风格,每项1句,按类型取舍)→ ③镜头叙事(每镜头1段流畅描述:机位/景别/朝向+动作/变化+运镜嵌在动作中+有台词时带语气)→ ④负面提示词(放最后,3.3标准模板)` +提示词内部结构(6层结构,按类型适配):`①主体列表(置顶,按类别分行:人物/场景/道具,@图片引用优先)→ ②画面质感(视觉风格+摄影质感参数,视觉基调前置)→ ③色调光影(光源+主光方向+亮暗区+色调/色温,1句)→ ④主体设定(人物逐人必写不可遗漏/场景/构图,每项1句,按类型取舍)→ ⑤镜头叙事(每镜头1段流畅描述:机位/景别/朝向+动作/变化+运镜嵌在动作中+有台词时带语气)→ ⑥负面提示词(放最后,3.3标准模板)` > 11字段(一致性锚定/类型质感/人物设定/场景/景深对焦/摄影质感/表演幅度/背景动态/镜头空间/表演指令/负面词)降为**构建时内部思考清单**,不作为输出字段标签。详见制作流程第三节 3.2。 @@ -287,7 +289,7 @@ F4 输出 = 主题 + 导演阐述 + 完整提示词(代码块包裹)。多 **剪辑节奏公式(>15秒必选):** 呼吸式 / 心跳式 / 海浪式 / 子弹时间 / 脉冲式 / 静默锤击 **详细方法论:** -- 创意策略+6步总流程+路径分流+4层结构(主体按类别分行+负面词放最后+镜头朝向规则,11字段降为内部思考清单)+标准负面词:`references/制作流程/分镜提示词制作流程.md` +- 创意策略+6步总流程+路径分流+6层结构(主体/画面质感/色调光影/主体设定/镜头叙事/负面词,视觉基调前置,11字段降为内部思考清单)+标准负面词:`references/制作流程/分镜提示词制作流程.md` - 长视频策略+生产流水线(25格聚合矩阵+节奏曲线+景别曲线+P0/P1/P2优先级):`references/制作流程/长视频分段制作流程.md` - 分镜板驱动(路径D格间关系三分类+三层动作分级):`references/制作流程/图生视频与分镜板制作流程.md` - 剪辑节奏(六大节奏公式+AI素材专属5对策+9步工作流):`references/制作流程/多镜头剪辑节奏流程.md` @@ -391,7 +393,7 @@ F4 输出 = 主题 + 导演阐述 + 完整提示词(代码块包裹)。多 |:---:|:---|:---| | 1 | 需求解析与参数确认 | 确认时长/比例/生成模式/风格/素材;智能推理原则——能推理的不追问,追问控制在 1-2 个问题内 | | 2 | 视觉诊断与分镜构思 | 三层知识库路由加载参考→构思分镜草案→选定导演风格与视觉方案 | -| 3 | 提示词构建 | 按4层结构撰写提示词(按类型适配选层;构建时用11字段思考清单内部过目),时间轴分镜,一镜一动 | +| 3 | 提示词构建 | 按6层结构撰写提示词(按类型适配选层;构建时用11字段思考清单内部过目),时间轴分镜,一镜一动 | | 4 | 强制自我校验 | 8 项校验规则逐条检查,有 error 必重写,全部通过才可交付 | | 5 | 专业交付 | 主题+导演阐述+完整提示词(代码块包裹),多段分镜附过渡策略 | @@ -403,9 +405,9 @@ F4 输出 = 主题 + 导演阐述 + 完整提示词(代码块包裹)。多 ### 提示词模板 -> 详见 `references/制作流程/分镜提示词制作流程.md` 第三节:3.1 基础六要素(构建思考参考)+ 3.2 4层结构(输出格式,含11字段内部思考清单 3.2.1 + 4层式输出 3.2.2含类型适配表,负面词放最后)+ 3.3 标准负面词模板。 +> 详见 `references/制作流程/分镜提示词制作流程.md` 第三节:3.1 基础六要素(构建思考参考)+ 3.2 6层结构(输出格式,含11字段内部思考清单 3.2.1 + 6层式输出 3.2.2含类型适配表,负面词放最后)+ 3.3 标准负面词模板。 -4层结构按Step 1类型判定适配:①主体列表(置顶,按类别分行:人物/场景/道具,@图片引用优先)+ ②设定(人物/场景/光线/构图/视觉风格,每项1句,按类型取舍)+ ③镜头叙事(每镜头1段流畅描述,朝向必须写明,连续动作不拆,有台词时带语气)+ ④负面提示词(放最后,3.3标准模板)。构建时用11字段思考清单内部过目确保不遗漏(一致性锚定/类型质感/人物设定/场景/景深对焦/摄影质感/表演幅度/背景动态/镜头空间/表演指令/负面词),但最终输出不出现字段标签。 +6层结构按Step 1类型判定适配:①主体列表(置顶,按类别分行:人物/场景/道具,@图片引用优先)+ ②画面质感(视觉风格+摄影质感参数,视觉基调前置)+ ③色调光影(光源+主光方向+亮暗区+色调/色温,1句)+ ④主体设定(人物/场景/构图,每项1句,按类型取舍)+ ⑤镜头叙事(每镜头1段流畅描述,朝向必须写明,连续动作不拆,有台词时带语气)+ ⑥负面提示词(放最后,3.3标准模板)。构建时用11字段思考清单内部过目确保不遗漏(一致性锚定/类型质感/人物设定/场景/景深对焦/摄影质感/表演幅度/背景动态/镜头空间/表演指令/负面词),但最终输出不出现字段标签。 ### 光影三层结构 @@ -478,7 +480,7 @@ F4 输出 = 主题 + 导演阐述 + 完整提示词(代码块包裹)。多 - [ ] ⑤ 资产引用限制:图片≤9、视频≤3、音频≤3、混合≤12 - [ ] ⑥ 冲突检测:运动冲突(快速+慢动作)、光学冲突(超广角+浅景深)、风格冲突(IMAX vs VHS 等) - [ ] ⑦ 裸英文运镜词检测:Dolly/Aerial/Crane/Pan/Arc/Dutch/Steadicam → 中文或完整短语 -- [ ] ⑧ 结构完整性:核心层(主体+负面词+镜头叙事)必须存在,②设定按类型取舍(A/B全用/C人物可选/D/E人物省略,详见制作流程第三节 3.2.2 类型适配表) +- [ ] ⑧ 结构完整性:核心层(主体+负面词+镜头叙事)必须存在,②画面质感/③色调光影全类型必用,④主体设定按类型取舍(A/B全用/C人物可选/D/E人物省略,详见制作流程第三节 3.2.2 类型适配表) **通用:** - [ ] 提示词有清晰主体 diff --git a/project/短视频脚本创作/V1.0/subskill/mcn-video-prompt/references/制作流程/分镜提示词制作流程.md b/project/短视频脚本创作/V1.0/subskill/mcn-video-prompt/references/制作流程/分镜提示词制作流程.md index 127dfe8..fb5932d 100644 --- a/project/短视频脚本创作/V1.0/subskill/mcn-video-prompt/references/制作流程/分镜提示词制作流程.md +++ b/project/短视频脚本创作/V1.0/subskill/mcn-video-prompt/references/制作流程/分镜提示词制作流程.md @@ -39,7 +39,7 @@ ``` 剧本分析 - ├─ 类型判定 → 4层格式适配(哪些层省略/变形,见3.2.2类型适配表) + ├─ 类型判定 → 6层格式适配(哪些层省略/变形,见3.2.2类型适配表) ├─①节奏+台词风格 → 路径分流 + 段落时长 + 台词语气修饰 ├─②爆点/视觉高潮 → 景别递进 + 运镜设计(A/B/C类) ├─③反转 → 镜头逻辑 + 首末帧(A类) @@ -61,7 +61,7 @@ > - 类型=D → 只做①,②-⑤跳过 > - ①节奏=视觉流:产品亮相→质地特写→涂抹过程→效果呈现 / 线性 / 铺陈(亮相)→蓄力(特写)→爆发(涂抹)→余韵(效果) → **路径A + 单段8秒** > - 台词风格=N/A(无对白,可选旁白) -> - 驱动4层格式:①主体=护肤品(列道具行) ②设定-人物省略 ③镜头=产品展示流程(非角色动作) +> - 驱动6层格式:①主体=护肤品(列道具行) ④主体设定-人物省略 ⑤镜头=产品展示流程(非角色动作) ### Step 2 需求分析 - 明确:时长、画幅(9:16 竖屏 / 16:9 横屏)、单镜头还是多镜头、有无参考素材 @@ -79,12 +79,12 @@ | D 分镜板驱动 | 分镜脚本/多格规划 | 格间关系三分类 | 《图生视频与分镜板制作流程》 | ### Step 4 提示词构建 -按第三节输出格式组装:按Step 1类型判定结果选择4层结构的适用层(见3.2.2类型适配表);构建时用11字段思考清单(3.2.1)内部过目确保不遗漏。词表从 `知识库/15维度` 各取所需。 +按第三节输出格式组装:按Step 1类型判定结果选择6层结构的适用层(见3.2.2类型适配表);构建时用11字段思考清单(3.2.1)内部过目确保不遗漏。词表从 `知识库/15维度` 各取所需。 **剧本分析(Step 1)产出是提示词组装的内容来源**:景别/运镜由爆点驱动、场景调度由人物关系驱动、微表情弧线由情绪弱点驱动、人物设定由角色定位驱动、表演指令由情绪弱点驱动、负面词由场景类型勾选。 ### Step 5 强制校验(8项) 1. **字符数**:Python `len()` 实测,禁止估算;品质基准 ≤500 字符,硬性上限 ≤2000 字符(实测) -2. **结构完整性**:输出必须包含核心层(①主体 + ②设定 + ③镜头叙事 + ④负面词),②设定层按类型适配(A全用/B全用/C人物可选/D人物省略/E人物省略);顺序固定主体→设定→镜头→负面词 +2. **结构完整性**:输出必须包含核心层(①主体 + ⑥负面词 + ⑤镜头叙事),②画面质感/③色调光影全类型必用,④主体设定层按类型适配(A全用/B全用/C人物可选/D人物省略/E人物省略);顺序固定主体→画面质感→色调光影→主体设定→镜头→负面词 3. **版权扫描**:品牌词/风格借喻/涉灰场景词/夜场BGM词 四类扫描(详见《图生视频与分镜板制作流程》版权审查) 4. **导演去名化**:提示词中不得出现导演名/工作室名/IP名,只用参数化描述 5. **动作物理合理性**:三层动作分级检查(大动作禁止) @@ -126,10 +126,10 @@ | 光影/风格/色彩 | 知识库/07~09、15 | | 音效(原生音效优先) | 知识库/13_音效音频 | -### 3.2 输出格式(4层结构) +### 3.2 输出格式(6层结构) > **实战验证**(2026-08-20):编导手写提示词(~350字,叙事驱动)生成效果优于结构化11字段模板(~700字,技术参数驱动)。**模型吃故事,不吃参数表。** -> **改造原则**:11字段降为构建时**内部思考清单**(确保不遗漏),最终输出只写**4层结构**——主体置顶按类别分行、设定按类型分项、镜头叙事流畅、负面词放最后。技术参数溶解进设定各分项,表演靠动作暗示而非禁令。按Step 1类型判定结果选择4层的适用层。 +> **改造原则**:11字段降为构建时**内部思考清单**(确保不遗漏),最终输出只写**6层结构**——主体置顶按类别分行、画面质感与色调光影紧跟主体前置(全局视觉基调前置,模型对提示词开头权重最高)、主体设定按类型分项、镜头叙事流畅、负面词放最后。技术参数溶解进②画面质感/③色调光影/④主体设定各分项,表演靠动作暗示而非禁令。按Step 1类型判定结果选择6层的适用层。 #### 3.2.1 构建思考清单(内部,不落盘) @@ -137,21 +137,21 @@ | 思考项 | 构建时想什么 | 输出时去哪 | |--------|-------------|-----------| -| ①一致性锚定 | 角色服装/道具是否需文字锁定? | ①主体列表@图片引用锁定;无图才在②设定-人物补1句 | -| ②类型质感 | 画质/风格/光影基调? | ②设定-视觉风格(1句) | -| ③人物设定 | 逐角色外貌/着装/气质? | ①主体列表@图片锁定;无图在②设定-人物补1句(**主体中每个人物必写,不可遗漏**) | -| ④场景 | 空间布局/道具? | ②设定-场景(1句空间描述) | -| ⑤景深对焦 | 是否需非默认景深? | **默认不写**;特写需浅景深时加进②设定-构图 | +| ①一致性锚定 | 角色服装/道具是否需文字锁定? | ①主体列表@图片引用锁定;无图才在④主体设定-人物补1句 | +| ②类型质感 | 画质/风格/光影基调? | ②画面质感-视觉风格(1句) | +| ③人物设定 | 逐角色外貌/着装/气质? | ①主体列表@图片锁定;无图在④主体设定-人物补1句(**主体中每个人物必写,不可遗漏**) | +| ④场景 | 空间布局/道具? | ④主体设定-场景(1句空间描述) | +| ⑤景深对焦 | 是否需非默认景深? | **默认不写**;特写需浅景深时加进④主体设定-构图 | | ⑥摄影质感 | 机型/镜头/光圈/快门/ISO/色彩/光影/质感/动态范围? | 按维度自动生成英文摄影参数(从知识库/10_后期细节·摄影质感维度表取值,判断适用维度组合),排除画幅/帧率 | | ⑦表演幅度 | 需禁某种表演? | **默认不写**;用动作暗示替代("鬼鬼祟祟地说"比"禁夸张表情"有效) | -| ⑧背景动态 | 需环境微动? | **默认不写**;仅在画面可能静止时加进③镜头动作描述 | -| ⑨镜头空间 | 机位/景别/朝向? | 溶进③镜头叙事("中景跟拍男主正面") | -| ⑩表演指令 | 逐beat微动作? | 溶进③镜头叙事的动作描述 | -| ⑪负面词 | 场景适配禁止项? | ④负面提示词(3.3标准模板) | +| ⑧背景动态 | 需环境微动? | **默认不写**;仅在画面可能静止时加进⑤镜头动作描述 | +| ⑨镜头空间 | 机位/景别/朝向? | 溶进⑤镜头叙事("中景跟拍男主正面") | +| ⑩表演指令 | 逐beat微动作? | 溶进⑤镜头叙事的动作描述 | +| ⑪负面词 | 场景适配禁止项? | ⑥负面提示词(3.3标准模板) | > **原则:能用@图片引用解决的,绝不写文字;能用动作暗示的,绝不写技术参数。** -#### 3.2.2 输出格式(4层结构) +#### 3.2.2 输出格式(6层结构) ``` ①主体列表(置顶,按类别分行) @@ -162,18 +162,24 @@ > 有哪类列哪类,无道具/无场景则省略该行;D类主体=产品(列道具行),E类主体=场景(列场景行) > **人物锚定句(仅多角色+跨镜头复用时启用)**:`将 @图片N 中的[身份/外观/服装]定义为 <角色名>`,后续镜头用 `<角色名>` 指代、不再重复写外貌;**识别不到角色名时用角色代称锚定**(`<男主>` `<兄弟>` `<角色A>` 等代称);单人单段保持现状(`人物:男主 @图片2`),不写锚定句 -②设定(分项,每项1句,按类型取舍) +②画面质感(视觉基调前置,紧跟主体) +视觉风格:[画质+风格+质感1句] +摄影质感:模型按维度自动生成英文摄影参数(机型/镜头/光圈/快门/ISO/色彩/光影/质感/动态范围),**用[]框起**,根据剧情场景判断适用维度,从知识库/10_后期细节·摄影质感维度表取值组合(不照搬固定示例);排除画幅/帧率(属平台参数放代码框外) +> 风格见知识库/15_视觉风格(去名化) + +③色调光影(光影基调前置) +色调光影:[光源来源+主光方向+亮暗区+色调/色温1句] +> 光影三层(光源层/光行为层/色调层)见知识库/07_光线时间 + +④主体设定(分项,每项1句,按类型取舍) 人物:[角色]——[气质/状态1句];[角色]——[气质/状态1句] > **主体中出现的每个人物,此处必须有对应设定,不可遗漏**——逐人写,用分号分隔 > @图片已锁定的不重复写外貌,只补气质/状态;无图时按知识库/01_主体描述外貌原则写(宁少写不错写);D/E类省略此行 场景:[空间布局+前中后景层次1句] -光线:[光源来源+主光方向+亮暗区+色温1句] 构图:[景别+前中后景层次+构图法则+视觉重心/留白1句] -视觉风格:[画质+风格+质感1句] -摄影质感:模型按维度自动生成英文摄影参数(机型/镜头/光圈/快门/ISO/色彩/光影/质感/动态范围),**用[]框起**,根据剧情场景判断适用维度,从知识库/10_后期细节·摄影质感维度表取值组合(不照搬固定示例);排除画幅/帧率(属平台参数放代码框外) -> 场景见知识库/03_场景描述(空间锚定三层);光线见知识库/07_光线时间(光影三层);构图见知识库/14_构图布局(五法则+构图几何+雷区规避);风格见知识库/15_视觉风格(去名化);朝向见知识库/06_焦距视角X轴 +> 场景见知识库/03_场景描述(空间锚定三层);构图见知识库/14_构图布局(五法则+构图几何+雷区规避);朝向见知识库/06_焦距视角X轴 -③镜头(每镜头1段流畅叙事) +⑤镜头(每镜头1段流畅叙事) 镜头N:[机位/景别/朝向] + [动作/变化流程] + [运镜变化嵌在动作里] + [有台词时:(人物,语气):内容] - 每镜头1段,按动作流自然叙述,不分beat罗列 - **朝向必须写明**:正面/背后/固定/侧面——用"正面拍摄""从背后拍摄""固定视角"等明确朝向(词库见知识库/06_焦距视角X轴+知识库/03_场景描述面部朝向/过肩);**主体位置用画面坐标**——写"男主位于画面右侧、四分之三侧朝镜头",不写"从男主右侧拍摄"(画面坐标优于主体坐标,生视频模型更易定位,词库见知识库/03_场景描述演员调度);**避开构图雷区**——默认不用"正面居中",改用 X2 四分之三侧/三分法偏移(雷区见知识库/14_构图布局) @@ -192,28 +198,28 @@ - **段末状态(仅多段连续输出时写,单段不写)**:每段末尾写一句"下一段可继承的可见状态"——角色姿态/视线方向/道具位置/光线方向/镜头方向,如"段末男主缩在门框边,薯片袋已被兄弟抢走,镜头停在两人对峙的正面"。用于跨段连续性的锚点 > D/E类镜头层写产品展示流程或氛围流变,不写角色动作 -④负面提示词 +⑥负面提示词 负面提示词:[通用项] + [场景勾选项](见3.3) > 负面词放最后——镜头叙事写完后整体排除 > BGM禁止由负面词覆盖(通用项已含"背景音乐,BGM,配乐"),不需要单独音频段 ``` -> **4层×5类型适配表(按Step 1类型判定结果选层):** +> **6层×5类型适配表(按Step 1类型判定结果选层):** -| 类型 | ①主体 | ②设定-人物 | ②设定-其余4项 | ③镜头-台词 | ③镜头-动作 | -|------|-------|-----------|-------------|-----------|-----------| -| A 叙事剧情 | 人物 | 全用 | 全用 | 对白格式 | 角色动作 | -| B 单人Vlog | 人物 | 全用 | 全用 | 口播(非对白) | 人物动作 | -| C 旁白知识 | 场景/道具 | 可选 | 全用 | 旁白格式 | 画面变化 | -| D 产品展示 | 产品 | 省略 | 全用 | 无(可选旁白) | 产品展示流程 | -| E 纯氛围 | 场景 | 省略 | 全用 | 无 | 氛围流变 | +| 类型 | ①主体 | ②画面质感 | ③色调光影 | ④主体设定-人物 | ④主体设定-场景/构图 | ⑤镜头-台词 | ⑤镜头-动作 | +|------|-------|-----------|-----------|----------------|--------------------|-----------|-----------| +| A 叙事剧情 | 人物 | 全用 | 全用 | 全用 | 全用 | 对白格式 | 角色动作 | +| B 单人Vlog | 人物 | 全用 | 全用 | 全用 | 全用 | 口播(非对白) | 人物动作 | +| C 旁白知识 | 场景/道具 | 全用 | 全用 | 可选 | 全用 | 旁白格式 | 画面变化 | +| D 产品展示 | 产品 | 全用 | 全用 | 省略 | 全用 | 无(可选旁白) | 产品展示流程 | +| E 纯氛围 | 场景 | 全用 | 全用 | 省略 | 全用 | 无 | 氛围流变 | > **编导式 vs 旧11字段版对比:** > > | 对比项 | 编导式(新) | 11字段版(旧) | > |--------|-------------|---------------| > | 字段标签 | 无,信息溶解进叙事 | 【一致性锚定】【景深对焦】等11个标签 | -> | 技术参数 | 溶进②设定层各分项 | 独立字段罗列(机型/f值/快门等) | +> | 技术参数 | 溶进②画面质感/③色调光影/④主体设定各分项 | 独立字段罗列(机型/f值/快门等) | > | 表演控制 | 动作暗示("鬼鬼祟祟地说") | 禁令约束("禁夸张喜剧化表情") | > | 镜头组织 | 每镜头1段流畅叙事 | 时间戳逐beat分段罗列 | > | 字符量 | ~350字 | ~700字 | diff --git a/project/短视频脚本创作/V1.0/subskill/mcn-video-prompt/references/制作流程/长视频分段制作流程.md b/project/短视频脚本创作/V1.0/subskill/mcn-video-prompt/references/制作流程/长视频分段制作流程.md index 8f9f11c..5d02d2d 100644 --- a/project/短视频脚本创作/V1.0/subskill/mcn-video-prompt/references/制作流程/长视频分段制作流程.md +++ b/project/短视频脚本创作/V1.0/subskill/mcn-video-prompt/references/制作流程/长视频分段制作流程.md @@ -56,13 +56,13 @@ Z8→Z5→Z5↔Z4→Z4→Z3→Z2→Z1→Z3→Z7→Z8→Z9(远景开场→中 ## 四、一致性锁定(每段必须执行) -> 4层结构中,一致性靠@图片引用+设定层统一实现,不需要额外写锁定语句。 +> 6层结构中,一致性靠@图片引用+②画面质感/③色调光影/④主体设定统一实现,不需要额外写锁定语句。 -| 类型 | 执行方式 | 在4层结构中去哪 | +| 类型 | 执行方式 | 在6层结构中去哪 | |------|---------|----------------| -| 风格锁定 | 视觉风格跨段统一(首段定调后续段重复或引用) | ②设定-视觉风格 | +| 风格锁定 | 视觉风格跨段统一(首段定调后续段重复或引用) | ②画面质感-视觉风格 | | 角色锁定 | @图片引用在每段①主体列表重复出现 | ①主体列表 | -| 场景锁定 | 场景延续时②设定-场景写"延续上一段场景",新场景写新描述 | ②设定-场景 | +| 场景锁定 | 场景延续时④主体设定-场景写"延续上一段场景",新场景写新描述 | ④主体设定-场景 | ## 五、分段规则 @@ -107,18 +107,22 @@ Z8→Z5→Z5↔Z4→Z4→Z3→Z2→Z1→Z3→Z7→Z8→Z9(远景开场→中 ### 简单段 ``` ①主体列表(置顶,按类别分行:人物/场景/道具,@图片引用优先;D类主体=产品,E类主体=场景) -②设定(人物逐人必写不可遗漏/场景/光线/构图/视觉风格,每项1句,按类型取舍:D/E类省略人物;摄影质感默认生成参考值·[]框起) -③镜头叙事(1段流畅描述:机位/景别/朝向+动作/变化+运镜+有台词时带语气) -④负面提示词(放最后,3.3标准模板) +②画面质感(视觉风格1句+摄影质感默认生成参考值·[]框起) +③色调光影(光源+主光方向+亮暗区+色调/色温,1句) +④主体设定(人物逐人必写不可遗漏/场景/构图,每项1句,按类型取舍:D/E类省略人物) +⑤镜头叙事(1段流畅描述:机位/景别/朝向+动作/变化+运镜+有台词时带语气) +⑥负面提示词(放最后,3.3标准模板) ``` -> 按Step 1类型判定适配4层,详见分镜提示词制作流程3.2.2类型适配表 +> 按Step 1类型判定适配6层,详见分镜提示词制作流程3.2.2类型适配表 ### 复杂段(多角色/多场景切换时用) ``` ①主体列表(置顶,按类别分行:人物/场景/道具,@图片引用优先,跨段每段重复出现) -②设定(人物逐人必写不可遗漏/场景/光线/构图/视觉风格,每项1句;场景变化时写新场景,跨段统一项不重复;摄影质感默认生成参考值·[]框起) -③镜头叙事(每镜头1段,多角色互动按动作流叙述,运镜嵌在动作中,朝向必须写明,连续动作不拆,有台词时带语气) -④负面提示词(放最后,3.3标准模板) +②画面质感(视觉风格1句+摄影质感默认生成参考值·[]框起;跨段统一项不重复) +③色调光影(光源+主光方向+亮暗区+色调/色温,1句;跨段统一项不重复) +④主体设定(人物逐人必写不可遗漏/场景/构图,每项1句;场景变化时写新场景,跨段统一项不重复) +⑤镜头叙事(每镜头1段,多角色互动按动作流叙述,运镜嵌在动作中,朝向必须写明,连续动作不拆,有台词时带语气) +⑥负面提示词(放最后,3.3标准模板) ``` > 跨段一致性:角色@图片引用在每段重复出现,不需要额外写一致性锚定语句。
ID账号生成方式脚本标题生成时间${h}操作
暂无 AI 写脚本(完成改写的脚本会出现在这里)