fix(工作台): 去「AI改写脚本」措辞残留,统一为 AI脚本(生成分镜/表头/占位/文档)
- data-pages.js: 查看 tooltip/表头已改写/两处暂无占位/生成分镜 desc+prompt 改写脚本→AI脚本 - docs/AI会话任务输入输出对照.md: 改写对比弹窗→脚本对比弹窗、生成分镜输入/模板同步 - cdp-verify-gensb.mjs: 新增生成分镜措辞验证(hook confirmRun 断言无改写)
This commit is contained in:
1 parent
79b8a3d036
commit
cce27c0ba2
4 files changed
+128
-9
No files matched your search
@@ -169,3 +169,10 @@
|
||||
- **CDP 验证全 PASS**(cdp-verify-typebadge.mjs):badge 文本 ["内部","外部","外部","外部","外部","内部","内部"];下拉 ["全部","内部","外部"];UI(badge+下拉)无「签约」残留
|
||||
- **「签约」残留排查边界**:页面含「签约」文本来自账号 bio 数据(如「Oiiioii签约超创」简介),属账号数据不在改名范围——用 CDP 脚本定位(tmp/find-signed.mjs,跑完即删)确认为数据文本而非 UI
|
||||
- 临时脚本已删;待提交(与顺序统一+docs 文档一起按 2 小时节奏 push)
|
||||
|
||||
## 09-01 12:2x 去「AI改写脚本」措辞残留(用户:没有AI改写脚本的说法了)
|
||||
- **用户指出**:docs 生成分镜行「视频标题+AI改写脚本」过时——按钮已统一「AI创作」,无「改写」概念
|
||||
- **改动 8 处**:data-pages.js 6 处(L510 查看按钮 tooltip 查看改写脚本→查看AI脚本;L529 表头 已改写→AI脚本;L618/761 占位 暂无改写脚本→暂无AI脚本;L640/642 生成分镜 desc+prompt AI改写脚本→AI脚本(AI创作的故事脚本))+ docs 2 处(表格 L18 改写对比弹窗→脚本对比弹窗、输入=视频标题+AI脚本;L74 模板 AI改写脚本→AI脚本)
|
||||
- **L732 空状态**「完成改写的脚本会出现在这里」→「AI 创作的脚本会出现在这里」(功能名「AI写脚本」保留,待用户定夺统一为 AI创作)
|
||||
- **CDP 验证 PASS**(cdp-verify-gensb.mjs 新增):#/rewrites → 对比弹窗 → 重新生成分镜 → title=重新生成分镜/label=分镜指令/desc+prompt 含「AI 脚本」且无「改写」
|
||||
- 已 commit(c9db…,2 小时节奏内不 push)
|
||||
@@ -0,0 +1,112 @@
|
||||
/* cdp-verify-gensb.mjs — 验证「生成分镜」弹窗 desc/prompt 无「改写」措辞残留(09-01 用户:没有AI改写脚本的说法了)
|
||||
* 覆盖点:#/rewrites 页 → 查看(对比弹窗)→ 生成分镜 → confirmRun opts
|
||||
* 方法:CDP 9223 → 复用现有 8900 标签页 → hook window.confirmRun 捕获 opts(不真实执行任务)
|
||||
* 断言:title=生成分镜/重新生成分镜;desc/prompt 含「AI 脚本」;不含「改写」
|
||||
* 运行:node cdp-verify-gensb.mjs
|
||||
*/
|
||||
const CDP = 'http://127.0.0.1:9223';
|
||||
|
||||
async function getWs(targetId) {
|
||||
const tabs = await (await fetch(CDP + '/json')).json();
|
||||
const t = tabs.find((x) => x.id === targetId);
|
||||
if (!t) throw new Error('target not found: ' + targetId);
|
||||
return t.webSocketDebuggerUrl;
|
||||
}
|
||||
function cdp(ws, method, params = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const id = ++cdp._id;
|
||||
const w = new WebSocket(ws);
|
||||
const timer = setTimeout(() => { try { w.close(); } catch (e) {} reject(new Error('timeout: ' + method)); }, 30000);
|
||||
w.onopen = () => w.send(JSON.stringify({ id, method, params }));
|
||||
w.onmessage = (ev) => {
|
||||
const m = JSON.parse(ev.data);
|
||||
if (m.id !== id) return;
|
||||
clearTimeout(timer);
|
||||
try { w.close(); } catch (e) {}
|
||||
m.error ? reject(new Error(method + ': ' + JSON.stringify(m.error))) : resolve(m.result);
|
||||
};
|
||||
w.onerror = () => { clearTimeout(timer); reject(new Error('ws error: ' + method)); };
|
||||
});
|
||||
}
|
||||
cdp._id = 0;
|
||||
|
||||
async function evalJs(ws, expr) {
|
||||
const r = await cdp(ws, 'Runtime.evaluate', { expression: expr, awaitPromise: true, returnByValue: true });
|
||||
if (r.exceptionDetails) throw new Error('eval err: ' + JSON.stringify(r.exceptionDetails).slice(0, 300));
|
||||
return r.result && r.result.value;
|
||||
}
|
||||
|
||||
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
|
||||
async function waitFor(ws, expr, timeout = 40000, label = '') {
|
||||
const t0 = Date.now();
|
||||
while (Date.now() - t0 < timeout) {
|
||||
try { if (await evalJs(ws, expr)) return true; } catch (e) {}
|
||||
await sleep(800);
|
||||
}
|
||||
throw new Error('waitFor timeout: ' + label + ' -> ' + expr);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const tabs = await (await fetch(CDP + '/json')).json();
|
||||
const wb = tabs.find((t) => t.url.includes('localhost:8900') || t.url.includes('127.0.0.1:8900'));
|
||||
if (!wb) throw new Error('未找到 8900 工作台标签页,请先在内置浏览器打开工作台');
|
||||
const ws = await getWs(wb.id);
|
||||
console.log('▶ 复用标签页:', wb.url);
|
||||
|
||||
console.log('\n【#/rewrites → 对比弹窗 → 生成分镜】');
|
||||
await evalJs(ws, `location.hash = '#/rewrites'`);
|
||||
await cdp(ws, 'Page.reload', { ignoreCache: true });
|
||||
await waitFor(ws, `!!document.querySelector('[data-detail]')`, 40000, 'rewrites 列表渲染(含查看按钮)');
|
||||
|
||||
// 打开对比弹窗(取第一条有 rewrite 的视频)
|
||||
const hasView = await evalJs(ws, `document.querySelectorAll('[data-detail]').length`);
|
||||
console.log('可测「查看」按钮数:', hasView);
|
||||
if (!hasView) throw new Error('无可测视频(无 rewrite 数据)');
|
||||
await evalJs(ws, `document.querySelector('[data-detail]').click(); true`);
|
||||
await waitFor(ws, `!!document.querySelector('.rw-modal')`, 30000, '对比弹窗打开');
|
||||
|
||||
// 注入 hook(弹窗已打开,点击生成分镜前)
|
||||
await evalJs(ws, `window.__cap = null; window.__origConfirmRun = window.confirmRun;
|
||||
window.confirmRun = (o) => { window.__cap = o; }; true`);
|
||||
|
||||
// 优先点「生成分镜」(data-gen-sb);已有分镜则点「重新生成」(data-sgen)
|
||||
const btnInfo = await evalJs(ws, `(() => {
|
||||
const gen = document.querySelector('.rw-modal [data-gen-sb]');
|
||||
const sgen = document.querySelector('.rw-modal [data-sgen]');
|
||||
return { hasGen: !!gen, hasSgen: !!sgen };
|
||||
})()`);
|
||||
console.log('按钮: 生成分镜=', btnInfo.hasGen, '| 重新生成=', btnInfo.hasSgen);
|
||||
const clicked = await evalJs(ws, `(() => {
|
||||
const gen = document.querySelector('.rw-modal [data-gen-sb]');
|
||||
const sgen = document.querySelector('.rw-modal [data-sgen]');
|
||||
if (gen) { gen.click(); return 'gen'; }
|
||||
if (sgen) { sgen.click(); return 'sgen'; }
|
||||
return null;
|
||||
})()`);
|
||||
if (!clicked) throw new Error('未找到生成分镜按钮');
|
||||
console.log('点击按钮:', clicked);
|
||||
|
||||
await waitFor(ws, `!!window.__cap`, 30000, 'confirmRun 捕获');
|
||||
const cap = JSON.parse(await evalJs(ws, `JSON.stringify(window.__cap)`));
|
||||
const p = cap.prompt || '';
|
||||
const d = cap.desc || '';
|
||||
console.log(' title:', cap.title, '| label:', cap.label, '| target:', cap.target);
|
||||
console.log(' desc:', d);
|
||||
console.log(' prompt:', p);
|
||||
console.log(' desc 含「AI 脚本」:', d.includes('AI 脚本'));
|
||||
console.log(' prompt 含「AI 脚本」:', p.includes('AI 脚本'));
|
||||
console.log(' desc 含「改写」:', d.includes('改写'));
|
||||
console.log(' prompt 含「改写」:', p.includes('改写'));
|
||||
|
||||
const ok = /生成分镜|重新生成分镜/.test(cap.title)
|
||||
&& cap.label === '分镜指令'
|
||||
&& d.includes('AI 脚本') && !d.includes('改写')
|
||||
&& p.includes('AI 脚本') && !p.includes('改写');
|
||||
console.log(' ✅ 生成分镜措辞断言:', ok ? 'PASS' : 'FAIL');
|
||||
|
||||
await evalJs(ws, `window.confirmRun = window.__origConfirmRun; true`);
|
||||
console.log('\n==== 验证完成 ====');
|
||||
}
|
||||
|
||||
main().catch((e) => { console.error('❌', e.message); process.exit(1); });
|
||||
@@ -15,7 +15,7 @@
|
||||
| 2 | 账号详情(#/account/:id)视频列表 | AI创作 | 创作指令 | 账号名 + 【账号设定(人设卡)】+ 视频选题块(有「# 选题」段→直接用;无→【视频解析内容(供提炼选题)】+ 提炼指令)或 未解析→先解析再创作 | 全新故事脚本 | renderAccountDetail data-rewrite |
|
||||
| 3 | AI写脚本(#/rewrites)视频列表 | AI创作 | 创作指令 | 同 2(按 accountId 拉人设,带 video_url) | 全新故事脚本 | renderRewrites data-rewrite |
|
||||
| 4 | 账号详情(#/account/:id) | 提炼账号设定 | 提炼指令 | 账号名 | 人设卡(12段账号设定) | renderAccountDetail data-extract |
|
||||
| 5 | 改写对比弹窗(AI脚本 vs 源脚本) | 生成分镜 / 重新生成 | 分镜指令 | 视频标题 + AI改写脚本 | AI 分镜提示词(按分镜技能) | openRewriteCompare fireGenSb |
|
||||
| 5 | 脚本对比弹窗(AI脚本 vs 源脚本) | 生成分镜 / 重新生成 | 分镜指令 | 视频标题 + AI脚本(该条 AI 创作的故事脚本) | AI 分镜提示词(按分镜技能) | openRewriteCompare fireGenSb |
|
||||
| 6 | AI脚本诊断(#/reviews) | 复盘 | 复盘指令 | 该条 AI 脚本(后台按 mcn-script-review 技能执行) | 评分式复盘报告(AI/源/对比三模式) | renderReviews data-review-row |
|
||||
| 7 | 周榜(#/ranking / 首页) | 分析 / 关注 | 保存指令 | 账号名 + 主页 URL | 账号信息 + 视频列表入库 | confirmSaveAccount |
|
||||
| 8 | 账号列表(#/accounts) | 添加 | 添加指令 | 账号名称 + 账号地址(可选) | 网页采集账号信息 + 视频列表入库 | openImportAccounts |
|
||||
@@ -71,7 +71,7 @@
|
||||
|
||||
### 5 生成分镜(转换类,无账号设定/选题概念)
|
||||
```
|
||||
请将视频《{title}》的 AI 改写脚本{重新}展开成 AI 分镜提示词(按分镜技能生成镜头级描述)。
|
||||
请将视频《{title}》的 AI 脚本{重新}展开成 AI 分镜提示词(按分镜技能生成镜头级描述)。
|
||||
```
|
||||
|
||||
### 6 复盘评分(诊断类,由技能定义输入输出)
|
||||
|
||||
@@ -507,7 +507,7 @@ async function renderRewrites(view) {
|
||||
<td>${fmtDuration(v.duration)}</td>
|
||||
<td>${fmt(v.publish_time)}</td>
|
||||
<td>${(v.has_source || v.has_analysis) ? '✅已解析' : '未解析'}</td>
|
||||
<td>${v.rewrite_count > 0 ? `<button class="btn btn-sm btn-view" data-detail="${v.id}" title="查看改写脚本">${v.rewrite_count} 查看</button>` : '—'}</td>
|
||||
<td>${v.rewrite_count > 0 ? `<button class="btn btn-sm btn-view" data-detail="${v.id}" title="查看 AI 脚本">${v.rewrite_count} 查看</button>` : '—'}</td>
|
||||
<td class="cell-act"><button class="btn btn-sm" data-rewrite='${esc(JSON.stringify({ id: v.id, name: v.account_name, title: cleanTitle(v.video_title), accountId: v.account_id, parsed: !!(v.has_source || v.has_analysis), url: v.video_url || '' }))}'>AI创作</button></td>
|
||||
</tr>`).join('');
|
||||
view.innerHTML = `
|
||||
@@ -526,7 +526,7 @@ async function renderRewrites(view) {
|
||||
</div>
|
||||
<div class="toolbar-note">每账号近期 30W+ 且评论 1W+ 取 10 条</div>
|
||||
<div class="table-wrap"><table class="table">
|
||||
<thead><tr><th>账号</th><th>视频标题</th><th>点赞</th><th>评论</th><th>分享</th><th>时长</th><th>发布时间</th><th>状态</th><th>已改写</th><th class="th-act">操作</th></tr></thead>
|
||||
<thead><tr><th>账号</th><th>视频标题</th><th>点赞</th><th>评论</th><th>分享</th><th>时长</th><th>发布时间</th><th>状态</th><th>AI脚本</th><th class="th-act">操作</th></tr></thead>
|
||||
<tbody>${rows || '<tr><td colspan="10" class="empty">暂无视频数据</td></tr>'}</tbody>
|
||||
</table></div>
|
||||
${pagerHtml(d.total, d.page, state.pageSize, (p) => { state.page = p; load(); })}
|
||||
@@ -615,7 +615,7 @@ async function renderRewrites(view) {
|
||||
</div>
|
||||
<div class="rw-sec">
|
||||
<div class="rw-sec-head">AI脚本<button class="btn btn-sm btn-view rw-src-toggle${showSrc ? ' src-on' : ''}" data-src-toggle title="${showSrc ? '收起源脚本' : '展开源脚本'}">${svgIcon('file-text', 14, 'currentColor')}</button></div>
|
||||
<div class="rw-sec-body md-body">${cur ? renderMarkdown(cur.script_text || '(暂无内容)') : '(暂无改写脚本)'}</div>
|
||||
<div class="rw-sec-body md-body">${cur ? renderMarkdown(cur.script_text || '(暂无内容)') : '(暂无 AI 脚本)'}</div>
|
||||
${verCtrl('v', verIdx, rewrites.length, '版本')}
|
||||
</div>
|
||||
<div class="rw-sec">
|
||||
@@ -637,9 +637,9 @@ async function renderRewrites(view) {
|
||||
const fireGenSb = (btn, regenerate) => {
|
||||
confirmRun({
|
||||
title: regenerate ? '重新生成分镜' : '生成分镜',
|
||||
desc: '将基于该 AI 改写脚本,展开成 AI 分镜提示词(按分镜技能生成镜头级描述)。',
|
||||
desc: '将基于该 AI 脚本(AI 创作的故事脚本),展开成 AI 分镜提示词(按分镜技能生成镜头级描述)。',
|
||||
target: `《${title}》`,
|
||||
prompt: `请将视频《${title}》的 AI 改写脚本${regenerate ? '重新' : ''}展开成 AI 分镜提示词(按分镜技能生成镜头级描述)。`,
|
||||
prompt: `请将视频《${title}》的 AI 脚本${regenerate ? '重新' : ''}展开成 AI 分镜提示词(按分镜技能生成镜头级描述)。`,
|
||||
label: '分镜指令',
|
||||
btn,
|
||||
});
|
||||
@@ -729,7 +729,7 @@ async function renderReviews(view) {
|
||||
<div class="page-toolbar">${toolbarBack()}<span class="page-title">AI脚本诊断</span><span class="pager-info">按生成时间倒序 · 共 ${items.length} 条</span></div>
|
||||
<div class="table-wrap"><table class="table">
|
||||
<thead><tr><th>ID</th><th>账号</th><th>生成方式</th><th>脚本标题</th><th>生成时间</th>${DIMS.map((h) => `<th style="text-align:center">${h}</th>`).join('')}<th>操作</th></tr></thead>
|
||||
<tbody>${rows || '<tr><td colspan="13" class="empty">暂无 AI 写脚本(完成改写的脚本会出现在这里)</td></tr>'}</tbody>
|
||||
<tbody>${rows || '<tr><td colspan="13" class="empty">暂无 AI 写脚本(AI 创作的脚本会出现在这里)</td></tr>'}</tbody>
|
||||
</table></div>
|
||||
</div>`;
|
||||
bindBack(view);
|
||||
@@ -758,7 +758,7 @@ async function renderReviews(view) {
|
||||
<div class="modal-title-row"><span>${esc(fmtTitleQ(row.title || ''))}</span><button class="btn" data-x>${svgIcon('x', 14)}</button></div>
|
||||
<div class="rw-split">
|
||||
<div class="rw-pane"><div class="rw-pane-title">源脚本</div><div class="rw-content md-body">${renderMarkdown(d2.source || '(暂无源脚本数据)')}</div></div>
|
||||
<div class="rw-pane"><div class="rw-pane-title">AI脚本</div><div class="rw-content md-body">${cur ? renderMarkdown(cur.script_text || '(暂无内容)') : '(暂无改写脚本)'}</div></div>
|
||||
<div class="rw-pane"><div class="rw-pane-title">AI脚本</div><div class="rw-content md-body">${cur ? renderMarkdown(cur.script_text || '(暂无内容)') : '(暂无 AI 脚本)'}</div></div>
|
||||
</div>
|
||||
</div>`;
|
||||
document.body.appendChild(mask);
|
||||
|
||||
Reference in new issue
Block a user