feat(工作台): AI创作输入输出梳理+顺序统一(账号设定前置)+类型列签约改内部
- data-pages.js: 未解析两分支账号设定前置; 账号详情 data-rewrite 补 url; 类型列 badge+筛选下拉 签约→内部(后端 type=signed 不变) - docs/AI会话任务输入输出对照.md: 8类功能入口输入输出+触发链路+prompt结构模板 - cdp-verify-aicreate.mjs: 断言升级(hasTopic动态分支+顺序断言) - cdp-verify-typebadge.mjs: 新增类型列改名验证(排除bio数据文本)
This commit is contained in:
1 parent
e91c8046c6
commit
79b8a3d036
5 files changed
+215
-7
No files matched your search
@@ -154,3 +154,18 @@
|
|||||||
- ② 账号详情旧梦留声机 id185(hasTopic=true):含【视频选题】+「# 选题」段(10106 字符)✅
|
- ② 账号详情旧梦留声机 id185(hasTopic=true):含【视频选题】+「# 选题」段(10106 字符)✅
|
||||||
- 未解析分支不变 PASS ✅
|
- 未解析分支不变 PASS ✅
|
||||||
- 教训:断言必须按数据实况动态分支(hasTopic true→查【视频选题】,false→查【视频解析内容】+提炼指令),写死单一路径会误报 FAIL
|
- 教训:断言必须按数据实况动态分支(hasTopic true→查【视频选题】,false→查【视频解析内容】+提炼指令),写死单一路径会误报 FAIL
|
||||||
|
|
||||||
|
## 全功能 AI 会话任务输入输出梳理 + 顺序统一(12:05-12:20)
|
||||||
|
- **用户需求**:①把每个功能对应的 AI 会话任务输入输出搞清楚 ②创作类 prompt 一般「账号设定放前面、选题信息放后面」
|
||||||
|
- **梳理产出**:`mcn-work-shop/docs/AI会话任务输入输出对照.md`(新建 docs/ 目录)——8 类功能入口对照表(账号列表 AI创作/账号详情视频列表 AI创作/#/rewrites AI创作/提炼账号设定/生成分镜/复盘评分/保存并分析账号/添加账号)+ 触发链路 + 各功能 prompt 结构模板 + 关键细节 + 验证说明
|
||||||
|
- **顺序统一改动(data-pages.js 2 处未解析分支)**:账号详情视频列表 L436 + #/rewrites L566 —— 未解析 prompt 原为「请先解析…再创作:URL\n标题…${personaBlock}」(账号设定在末尾),统一改为「请为达人…创作…。${personaBlock}${noPersonaNote}\n该视频尚未解析,请先解析…(视频地址+标题)…再基于账号设定与提炼出的选题创作」(账号信息前置)
|
||||||
|
- **补漏**:账号详情分支 data-rewrite JSON 缺 url 字段(#/rewrites 分支有),未解析 prompt 用 r.url 会丢视频地址 → 补齐 `url: v.video_url || ''`(L340)
|
||||||
|
- **验证**:cdp-verify-aicreate.mjs 升级断言——已解析 topic/content 两模式 + 未解析「账号信息(人设块或缺失提示)位置 < 解析要求位置」顺序断言;全部 PASS(①已解析旧梦留声机 1629 content 模式、②未解析白昼小熊缺失提示 idx26<解析要求 idx75、③账号详情旧梦留声机 topic 模式)
|
||||||
|
- **已确认无需改**:confirmAiScript(账号列表)本就是 人设→选题方向 顺序;生成分镜/提炼设定/复盘/保存添加为转换/诊断类任务,无账号设定-选题顺序概念
|
||||||
|
|
||||||
|
## 09-01 20:0x 账号列表「类型」列 签约→内部(badge + 筛选下拉)
|
||||||
|
- **用户需求**:「把账号列表下的 类型列的签约改为 内部」
|
||||||
|
- **改动(data-pages.js 2 处文案)**:L237 badge `${t === 'external' ? '外部' : '签约'}`→`'内部'`;L260 筛选下拉 option「签约」→「内部」;后端 type=signed 值不变
|
||||||
|
- **CDP 验证全 PASS**(cdp-verify-typebadge.mjs):badge 文本 ["内部","外部","外部","外部","外部","内部","内部"];下拉 ["全部","内部","外部"];UI(badge+下拉)无「签约」残留
|
||||||
|
- **「签约」残留排查边界**:页面含「签约」文本来自账号 bio 数据(如「Oiiioii签约超创」简介),属账号数据不在改名范围——用 CDP 脚本定位(tmp/find-signed.mjs,跑完即删)确认为数据文本而非 UI
|
||||||
|
- 临时脚本已删;待提交(与顺序统一+docs 文档一起按 2 小时节奏 push)
|
||||||
@@ -119,8 +119,16 @@ async function main() {
|
|||||||
await evalJs(ws, `document.querySelectorAll('[data-rewrite]')[${unparsedInfo.idx}].click(); true`);
|
await evalJs(ws, `document.querySelectorAll('[data-rewrite]')[${unparsedInfo.idx}].click(); true`);
|
||||||
await waitFor(ws, `!!window.__cap`, 30000, 'confirmRun 捕获(未解析)');
|
await waitFor(ws, `!!window.__cap`, 30000, 'confirmRun 捕获(未解析)');
|
||||||
const p2 = JSON.parse(await evalJs(ws, `JSON.stringify(window.__cap)`)).prompt || '';
|
const p2 = JSON.parse(await evalJs(ws, `JSON.stringify(window.__cap)`)).prompt || '';
|
||||||
const ok2 = p2.includes('请先解析这个抖音视频') && !p2.includes('undefined');
|
// 账号设定在前:账号相关信息(【账号设定(人设卡)】块,或无人设时的「该账号暂无账号设定」提示)必须出现在解析要求之前(09-01 用户要求统一顺序)
|
||||||
console.log(' 未解析分支(先解析再创作+无 undefined 泄漏):', ok2 ? 'PASS' : 'FAIL');
|
const iP2 = p2.indexOf('【账号设定(人设卡)】');
|
||||||
|
const iNote = p2.indexOf('该账号暂无账号设定');
|
||||||
|
const iUn = p2.indexOf('该视频尚未解析');
|
||||||
|
const acctPos = iP2 >= 0 ? iP2 : iNote; // 有 persona 取人设块位置;无 persona 取缺失提示位置
|
||||||
|
const ok2 = p2.includes('该视频尚未解析,请先解析')
|
||||||
|
&& acctPos >= 0 && iUn > acctPos // 账号相关信息在解析要求之前
|
||||||
|
&& !p2.includes('undefined');
|
||||||
|
console.log(' 未解析分支:人设块 idx=', iP2, '| 缺失提示 idx=', iNote, '| 解析要求 idx=', iUn);
|
||||||
|
console.log(' 未解析分支(账号信息前置+先解析再创作+无泄漏):', ok2 ? 'PASS' : 'FAIL');
|
||||||
} else {
|
} else {
|
||||||
console.log(' 无未解析视频可测(跳过)');
|
console.log(' 无未解析视频可测(跳过)');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
/* cdp-verify-typebadge.mjs — 账号列表「类型」列 签约→内部 改名验证(09-01)
|
||||||
|
* 断言:①badge 文本(非 external)=「内部」、external=「外部」不变 ②筛选下拉选项「内部」③页面无「签约」残留
|
||||||
|
* 运行:node cdp-verify-typebadge.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);
|
||||||
|
|
||||||
|
await evalJs(ws, `location.hash = '#/accounts'`);
|
||||||
|
await cdp(ws, 'Page.reload', { ignoreCache: true });
|
||||||
|
await waitFor(ws, `!!document.querySelector('[data-create]')`, 40000, '账号列表渲染');
|
||||||
|
await sleep(500);
|
||||||
|
|
||||||
|
const r = await evalJs(ws, `(() => {
|
||||||
|
const badges = [...document.querySelectorAll('.badge')].map((b) => b.textContent.trim());
|
||||||
|
const sel = document.querySelector('#accType');
|
||||||
|
const opts = sel ? [...sel.options].map((o) => o.textContent.trim()) : [];
|
||||||
|
// UI 残留检查仅限 badge 与筛选下拉(账号 bio 简介等数据文本含「签约」属账号数据,不在改名范围)
|
||||||
|
const badgeText = document.querySelector('.table-wrap') ? [...document.querySelectorAll('.badge')].map((b) => b.textContent).join('') : '';
|
||||||
|
const selText = sel ? sel.textContent : '';
|
||||||
|
return { badges, opts, uiSigned: (badgeText + selText).includes('签约') };
|
||||||
|
})()`);
|
||||||
|
console.log('badge 文本:', JSON.stringify(r.badges));
|
||||||
|
console.log('筛选下拉选项:', JSON.stringify(r.opts));
|
||||||
|
console.log('UI(badge+下拉)含「签约」残留:', r.uiSigned);
|
||||||
|
|
||||||
|
const extOk = r.badges.includes('外部');
|
||||||
|
const intOk = r.badges.some((b) => b === '内部');
|
||||||
|
const optOk = r.opts.includes('内部') && !r.opts.includes('签约');
|
||||||
|
const noSigned = !r.uiSigned;
|
||||||
|
console.log('✅ 外部 badge 保留:', extOk ? 'PASS' : 'FAIL');
|
||||||
|
console.log('✅ 内部 badge 出现:', intOk ? 'PASS' : 'FAIL');
|
||||||
|
console.log('✅ 下拉含「内部」且无「签约」:', optOk ? 'PASS' : 'FAIL');
|
||||||
|
console.log('✅ UI 无「签约」残留:', noSigned ? 'PASS' : 'FAIL');
|
||||||
|
console.log('\n====', (extOk && intOk && optOk && noSigned) ? '全部 PASS ====' : '存在 FAIL ====');
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((e) => { console.error('❌', e.message); process.exit(1); });
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
# MCN 工作台 · AI 会话任务输入输出对照
|
||||||
|
|
||||||
|
> 更新:2026-09-01 | 范围:工作台所有触发 AI 会话任务(/api/run → 左侧会话栏后台会话)的功能入口
|
||||||
|
> 统一规则(用户定):**创作类任务 prompt 一律「账号设定(人设卡)在前,选题信息在后」**;无人设时以「该账号暂无账号设定,请先提取」提示占位(同样前置)。
|
||||||
|
|
||||||
|
## 触发链路(所有入口共用)
|
||||||
|
|
||||||
|
按钮点击 → 确认弹窗(confirmRun / confirmAiScript / confirmSaveAccount)→ `executeTask()` → `POST /api/run`(server.js 写 automations 表,scheduledAt=now+5s + next_run_at=now+5000)→ 客户端调度器拾取 → 左侧会话栏「mcn-workshop」空间后台会话执行 → 轮询 `/api/run/status`(done/error/review)→ 完成刷新页面。
|
||||||
|
|
||||||
|
## 功能对照表
|
||||||
|
|
||||||
|
| # | 功能入口 | 按钮 | 任务名(label) | 输入(prompt 注入) | 输出 | 代码位置 |
|
||||||
|
|---|---------|------|--------------|--------------------|------|---------|
|
||||||
|
| 1 | 账号列表(#/accounts) | AI创作 | 创作指令 | 账号名 + 【账号设定(人设卡)】(完整、剔非创作章节) + 选题方向(手输 或 AI 结合人设生成) | 全新故事脚本 | confirmAiScript |
|
||||||
|
| 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 |
|
||||||
|
| 6 | AI脚本诊断(#/reviews) | 复盘 | 复盘指令 | 该条 AI 脚本(后台按 mcn-script-review 技能执行) | 评分式复盘报告(AI/源/对比三模式) | renderReviews data-review-row |
|
||||||
|
| 7 | 周榜(#/ranking / 首页) | 分析 / 关注 | 保存指令 | 账号名 + 主页 URL | 账号信息 + 视频列表入库 | confirmSaveAccount |
|
||||||
|
| 8 | 账号列表(#/accounts) | 添加 | 添加指令 | 账号名称 + 账号地址(可选) | 网页采集账号信息 + 视频列表入库 | openImportAccounts |
|
||||||
|
|
||||||
|
## 各功能 prompt 结构与顺序
|
||||||
|
|
||||||
|
### 1/2/3 创作类(账号设定前 → 选题后)
|
||||||
|
|
||||||
|
**已解析 + 有「# 选题」段**(mode=topic):
|
||||||
|
```
|
||||||
|
请为达人「{name}」创作一条全新的短视频故事脚本。
|
||||||
|
【账号设定(人设卡)】
|
||||||
|
{人设卡全文,剔「## 十一、」及之后}
|
||||||
|
【视频选题】
|
||||||
|
标题:《{title}》
|
||||||
|
# 选题
|
||||||
|
{选题段:赛道/受众/视频主题/开场钩子/中间留人/结尾落点}
|
||||||
|
请结合账号设定与视频选题,创作一个全新的故事脚本(新故事、新情节,不要复刻原视频),输出完整故事脚本。
|
||||||
|
```
|
||||||
|
|
||||||
|
**已解析 + 无「# 选题」段**(老视频脚本=分镜头表,mode=content):
|
||||||
|
```
|
||||||
|
请为达人「{name}」创作一条全新的短视频故事脚本。
|
||||||
|
【账号设定(人设卡)】
|
||||||
|
{人设卡全文}
|
||||||
|
【视频解析内容(供提炼选题)】
|
||||||
|
标题:《{title}》
|
||||||
|
{解析 content 全文}
|
||||||
|
请先通读以上【视频解析内容】,提炼出该视频的选题(赛道/受众/视频主题/开场钩子/中间留人/结尾落点等),再结合账号设定与提炼出的选题,创作一个全新的故事脚本(新故事、新情节,不要复刻原视频),输出完整故事脚本。
|
||||||
|
```
|
||||||
|
|
||||||
|
**未解析**(先解析再创作,账号信息仍前置):
|
||||||
|
```
|
||||||
|
请为达人「{name}」创作一条全新的短视频故事脚本。
|
||||||
|
【账号设定(人设卡)】 或 「该账号暂无账号设定,请先为达人「{name}」提取账号设定(生成人设卡),再基于账号设定与选题创作。」
|
||||||
|
该视频尚未解析,请先解析这个抖音视频并做拆解分析(视频地址:{url}
|
||||||
|
视频标题:《{title}》),再基于账号设定与提炼出的选题,创作完整故事脚本。
|
||||||
|
```
|
||||||
|
|
||||||
|
**账号列表入口(confirmAiScript,选题方向)**:
|
||||||
|
```
|
||||||
|
请为达人「{name}」创作一条短视频脚本。
|
||||||
|
【账号设定(人设卡)】
|
||||||
|
{人设卡全文}
|
||||||
|
选题方向:{topic}(可选)
|
||||||
|
请贴合上述账号设定(人设与内容规则)创作。
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4 提炼账号设定
|
||||||
|
```
|
||||||
|
请为达人「{name}」提炼账号设定(生成人设卡)。
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5 生成分镜(转换类,无账号设定/选题概念)
|
||||||
|
```
|
||||||
|
请将视频《{title}》的 AI 改写脚本{重新}展开成 AI 分镜提示词(按分镜技能生成镜头级描述)。
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6 复盘评分(诊断类,由技能定义输入输出)
|
||||||
|
```
|
||||||
|
请对这条 AI 脚本做复盘评分(AI脚本 / 原视频脚本 / 对比分析三种模式),按 mcn-script-review(脚本复盘)技能执行。
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7 保存并分析账号
|
||||||
|
```
|
||||||
|
请帮我把抖音账号「{name}」保存并分析(账号信息 + 视频列表):{url}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8 添加账号
|
||||||
|
```
|
||||||
|
请帮我把抖音账号 {账号名称「x」, 账号地址「y」} 添加:通过抖音网页获取账号信息与视频列表,写入账号列表并保存到本地
|
||||||
|
```
|
||||||
|
|
||||||
|
## 关键细节
|
||||||
|
|
||||||
|
- **人设卡注入**:完整拼入(不截断),仅通过 `personaForCreate()` 剔除「## 十一、当前需解决的问题」及之后章节(分析师视角内容非创作素材);三处创作入口统一。
|
||||||
|
- **视频选题提取**:`extractVideoTopic(content)` 取解析 content 的「# 选题」段(到下一个 `\n# ` 标题止);无该段(老脚本为分镜头表格式)→ `buildTopicBlock` mode=content 携带全文 + 提炼指令,AI 先提炼选题再创作。
|
||||||
|
- **按钮命名**:全部统一「AI创作」(2026-09-01),弹窗标题同;「提炼账号设定」「生成分镜」「复盘」「分析/关注」「添加」为各自独立任务。
|
||||||
|
- **未解析分支**:带 video_url 时 prompt 内嵌「视频地址:{url}」;账号列表视频(#/rewrites)用 r.url,账号详情视频列表用 r.aweme 拼 https://www.douyin.com/video/{aweme}(历史遗留,建议统一为 video_url)。
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
|
||||||
|
- 脚本:`cdp-verify-aicreate.mjs`(hook confirmRun 捕获 prompt,断言账号设定在前、选题在后、无 undefined 泄漏、未解析分支账号信息前置)
|
||||||
|
- 覆盖:①#/rewrites 已解析(topic/content 两模式)②#/rewrites 未解析 ③账号详情已解析
|
||||||
@@ -234,7 +234,7 @@ async function renderAccounts(view) {
|
|||||||
const tracks = (await api('/api/dsh/tracks').catch(() => ({ tracks: [] }))).tracks || [];
|
const tracks = (await api('/api/dsh/tracks').catch(() => ({ tracks: [] }))).tracks || [];
|
||||||
async function load() {
|
async function load() {
|
||||||
const d = await api('/api/dsh/accounts?type=' + state.type + '&search=' + encodeURIComponent(state.search) + '&track=' + encodeURIComponent(state.track) + '&page=' + state.page + '&pageSize=' + state.pageSize);
|
const d = await api('/api/dsh/accounts?type=' + state.type + '&search=' + encodeURIComponent(state.search) + '&track=' + encodeURIComponent(state.track) + '&page=' + state.page + '&pageSize=' + state.pageSize);
|
||||||
const badge = (t) => `<span class="badge ${t === 'external' ? 'badge-ext' : 'badge-signed'}">${t === 'external' ? '外部' : '签约'}</span>`;
|
const badge = (t) => `<span class="badge ${t === 'external' ? 'badge-ext' : 'badge-signed'}">${t === 'external' ? '外部' : '内部'}</span>`;
|
||||||
const rows = (d.items || []).map((a) => `<tr>
|
const rows = (d.items || []).map((a) => `<tr>
|
||||||
<td><button class="fav-btn" data-fav="${a.id}" data-fav-val="${a.favorite ? 1 : 0}" title="${a.favorite ? '取消关注' : '点击关注'}">${a.favorite ? '⭐' : '☆'}</button></td>
|
<td><button class="fav-btn" data-fav="${a.id}" data-fav-val="${a.favorite ? 1 : 0}" title="${a.favorite ? '取消关注' : '点击关注'}">${a.favorite ? '⭐' : '☆'}</button></td>
|
||||||
<td>${badge(a.account_type)}</td>
|
<td>${badge(a.account_type)}</td>
|
||||||
@@ -257,7 +257,7 @@ async function renderAccounts(view) {
|
|||||||
<div class="toolbar-right" style="margin-right:10px">
|
<div class="toolbar-right" style="margin-right:10px">
|
||||||
<select id="accType" class="input-select">
|
<select id="accType" class="input-select">
|
||||||
<option value="all" ${state.type === 'all' ? 'selected' : ''}>全部</option>
|
<option value="all" ${state.type === 'all' ? 'selected' : ''}>全部</option>
|
||||||
<option value="signed" ${state.type === 'signed' ? 'selected' : ''}>签约</option>
|
<option value="signed" ${state.type === 'signed' ? 'selected' : ''}>内部</option>
|
||||||
<option value="external" ${state.type === 'external' ? 'selected' : ''}>外部</option>
|
<option value="external" ${state.type === 'external' ? 'selected' : ''}>外部</option>
|
||||||
</select>
|
</select>
|
||||||
<select id="accTrack" class="input-select"><option value="">全部赛道</option>${tracks.map((t) => `<option value="${esc(t)}" ${t === state.track ? 'selected' : ''}>${esc(t)}</option>`).join('')}</select>
|
<select id="accTrack" class="input-select"><option value="">全部赛道</option>${tracks.map((t) => `<option value="${esc(t)}" ${t === state.track ? 'selected' : ''}>${esc(t)}</option>`).join('')}</select>
|
||||||
@@ -337,7 +337,7 @@ async function renderAccountDetail(view, id) {
|
|||||||
<td>${fmtWan(v.comment_count)}</td>
|
<td>${fmtWan(v.comment_count)}</td>
|
||||||
<td>${fmtWan(v.share_count)}</td>
|
<td>${fmtWan(v.share_count)}</td>
|
||||||
<td>${fmtDuration(v.duration)}</td>
|
<td>${fmtDuration(v.duration)}</td>
|
||||||
<td class="cell-act"><button class="btn btn-sm ${(v.has_source || v.has_analysis) ? 'btn-view' : ''}" data-rewrite='${esc(JSON.stringify({ id: v.id, name: acc.account_name, title: cleanTitle(v.video_title), aweme: v.aweme_id, parsed: !!(v.has_source || v.has_analysis) }))}'>AI创作</button></td>
|
<td class="cell-act"><button class="btn btn-sm ${(v.has_source || v.has_analysis) ? 'btn-view' : ''}" data-rewrite='${esc(JSON.stringify({ id: v.id, name: acc.account_name, title: cleanTitle(v.video_title), aweme: v.aweme_id, url: v.video_url || '', parsed: !!(v.has_source || v.has_analysis) }))}'>AI创作</button></td>
|
||||||
</tr>`).join('');
|
</tr>`).join('');
|
||||||
const tabBtns = [['info', '账号信息'], ['persona', '账号设定'], ['analysis', '数据分析']].map(([k, label]) => `<button class="tab ${tab === k ? 'tab-active' : ''}" data-tab="${k}">${label}</button>`).join('');
|
const tabBtns = [['info', '账号信息'], ['persona', '账号设定'], ['analysis', '数据分析']].map(([k, label]) => `<button class="tab ${tab === k ? 'tab-active' : ''}" data-tab="${k}">${label}</button>`).join('');
|
||||||
let panel = '';
|
let panel = '';
|
||||||
@@ -433,7 +433,7 @@ async function renderAccountDetail(view, id) {
|
|||||||
target: `达人「${r.name}」|选题《${r.title}》`,
|
target: `达人「${r.name}」|选题《${r.title}》`,
|
||||||
prompt: parsed
|
prompt: parsed
|
||||||
? `请为达人「${r.name}」创作一条全新的短视频故事脚本。${personaBlock}${noPersonaNote}${tb.block}${createTail}`
|
? `请为达人「${r.name}」创作一条全新的短视频故事脚本。${personaBlock}${noPersonaNote}${tb.block}${createTail}`
|
||||||
: `请先解析这个抖音视频并做拆解分析,再为达人「${r.name}」创作一条全新故事脚本:https://www.douyin.com/video/${r.aweme || ''}\n视频标题:《${r.title}》${personaBlock}${noPersonaNote}`,
|
: `请为达人「${r.name}」创作一条全新的短视频故事脚本。${personaBlock}${noPersonaNote}\n该视频尚未解析,请先解析这个抖音视频并做拆解分析(${r.url ? '视频地址:' + r.url + '\n' : ''}视频标题:《${r.title}》),再基于账号设定与提炼出的选题,创作完整故事脚本。`,
|
||||||
label: '创作指令',
|
label: '创作指令',
|
||||||
btn: b,
|
btn: b,
|
||||||
});
|
});
|
||||||
@@ -563,7 +563,7 @@ async function renderRewrites(view) {
|
|||||||
target: `达人「${r.name}」|选题《${r.title}》`,
|
target: `达人「${r.name}」|选题《${r.title}》`,
|
||||||
prompt: parsed
|
prompt: parsed
|
||||||
? `请为达人「${r.name}」创作一条全新的短视频故事脚本。${personaBlock}${noPersonaNote}${tb.block}${createTail}`
|
? `请为达人「${r.name}」创作一条全新的短视频故事脚本。${personaBlock}${noPersonaNote}${tb.block}${createTail}`
|
||||||
: `请先解析这个抖音视频并做拆解分析,再为达人「${r.name}」创作一条全新故事脚本${r.url ? ':' + r.url : ''}\n视频标题:《${r.title}》${personaBlock}${noPersonaNote}`,
|
: `请为达人「${r.name}」创作一条全新的短视频故事脚本。${personaBlock}${noPersonaNote}\n该视频尚未解析,请先解析这个抖音视频并做拆解分析(${r.url ? '视频地址:' + r.url + '\n' : ''}视频标题:《${r.title}》),再基于账号设定与提炼出的选题,创作完整故事脚本。`,
|
||||||
label: '创作指令',
|
label: '创作指令',
|
||||||
btn: b,
|
btn: b,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in new issue
Block a user