diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/build-create-prompts.cjs b/project/短视频脚本创作/V1.0/mcn-work-shop/build-create-prompts.cjs index dc40fcb..aa30f12 100644 --- a/project/短视频脚本创作/V1.0/mcn-work-shop/build-create-prompts.cjs +++ b/project/短视频脚本创作/V1.0/mcn-work-shop/build-create-prompts.cjs @@ -95,7 +95,7 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); const resp = await fetch(API, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ prompt: t.prompt, name: '创作指令' }), + body: JSON.stringify({ prompt: t.prompt, name: '创作指令', skills: ['短视频脚本创作'] }), }); const d = await resp.json(); results.push({ videoId: t.videoId, id: d.id, ok: d.ok, error: d.error }); diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/poll-review-tasks.cjs b/project/短视频脚本创作/V1.0/mcn-work-shop/poll-review-tasks.cjs new file mode 100644 index 0000000..7ab788e --- /dev/null +++ b/project/短视频脚本创作/V1.0/mcn-work-shop/poll-review-tasks.cjs @@ -0,0 +1,39 @@ +/* poll-review-tasks.cjs — 轮询 6 条复盘任务(/api/run/status,ACCEPTED 已归 done)*/ +const TASKS = [ + { rw: 31, id: 'automation-1788251452496' }, + { rw: 30, id: 'automation-1788251455526' }, + { rw: 29, id: 'automation-1788251458549' }, + { rw: 28, id: 'automation-1788251461578' }, + { rw: 27, id: 'automation-1788251464600' }, + { rw: 26, id: 'automation-1788251467630' }, +]; +const state = {}; +TASKS.forEach((t) => (state[t.rw] = 'pending')); +const status = async (id) => { + try { + const r = await fetch('http://localhost:8900/api/run/status?id=' + encodeURIComponent(id)); + const d = await r.json(); + return d.state || 'unknown'; + } catch { return 'unknown'; } +}; +console.log('START polling 6 review tasks:', new Date().toLocaleTimeString('zh-CN')); +const started = Date.now(); +const t0 = Date.now(); +while (Date.now() - started < 60 * 60 * 1000) { + let allDone = true; + for (const t of TASKS) { + const s = await status(t.id); + state[t.rw] = s; + if (s !== 'done' && s !== 'error') allDone = false; + } + console.log(new Date().toLocaleTimeString('zh-CN'), '|', + TASKS.map((t) => 'rw' + t.rw + ':' + state[t.rw]).join(' ')); + if (allDone) { + const errs = TASKS.filter((t) => state[t.rw] === 'error'); + console.log(errs.length ? 'ALL DONE (with errors): ' + errs.map((t) => 'rw' + t.rw).join(',') : 'ALL DONE ✓'); + process.exit(0); + } + await new Promise((r) => setTimeout(r, 30000)); +} +console.log('TIMEOUT after 60min, last states:', JSON.stringify(state)); +process.exit(1); diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/probe-asar-skills.cjs b/project/短视频脚本创作/V1.0/mcn-work-shop/probe-asar-skills.cjs new file mode 100644 index 0000000..319f697 --- /dev/null +++ b/project/短视频脚本创作/V1.0/mcn-work-shop/probe-asar-skills.cjs @@ -0,0 +1,54 @@ +// 解析 app.asar 找 skills_json 消费逻辑 +const fs = require('fs'); +const path = require('path'); +const asarPath = 'C:/Users/maidou/AppData/Local/Programs/WorkBuddy/resources/app.asar'; +const buf = fs.readFileSync(asarPath); + +// 新版 asar 多重 pickle 头:@0=4, @4=headerSize+4, @8=headerSize, @12=JSON长度, @16=JSON +const headerLen = buf.readUInt32LE(12); +let pos = 16; +const header = JSON.parse(buf.slice(pos, pos + headerLen).toString('utf8')); + +// 遍历文件树找含 skills_json 的 js 文件(全量,不限目录) +const hits = []; +function walk(node, prefix) { + if (!node.files) return; + for (const name of Object.keys(node.files)) { + const f = node.files[name]; + const full = prefix + '/' + name; + if (f.files) { walk(f, full); } + else if (/\.(js|cjs|mjs|ts)$/.test(name)) { + hits.push({ full, offset: f.offset, size: f.size }); + } + } +} +walk(header, ''); + +// 逐个读取含 skills_json 的文件片段 +const TARGET = Buffer.from('skills_json'); +const results = []; +for (const h of hits) { + const start = pos + h.offset; + const chunk = buf.slice(start, start + h.size); + if (chunk.includes(TARGET)) { + // 找出 skills_json 前后各 400 字符上下文 + let idx = 0; + while ((idx = chunk.indexOf(TARGET, idx)) !== -1) { + const from = Math.max(0, idx - 500); + const to = Math.min(chunk.length, idx + 700); + results.push({ file: h.full, ctx: chunk.slice(from, to).toString('utf8') }); + idx += TARGET.length; + if (results.length > 40) break; + } + } + if (results.length > 40) break; +} +console.log('含 skills_json 的文件数:', new Set(results.map(r=>r.file)).size); +console.log('片段数:', results.length); +// 只输出含读写逻辑的片段(排除 DDL/列定义) +const interesting = results.filter(r => /INSERT|SELECT|UPDATE|JSON\.parse|=>|function|await|\.map\(|\.filter\(|include\(|find\(|query|execute/i.test(r.ctx) && !/CREATE TABLE|ALTER TABLE|addSql|statement-breakpoint|"name",\s*$/.test(r.ctx)); +console.log('读写逻辑片段数:', interesting.length); +interesting.slice(0, 25).forEach((r, i) => { + console.log('\n########## ' + r.file + ' [' + i + '] ##########'); + console.log(r.ctx.replace(/[ \t]+/g, ' ').slice(0, 1600)); +}); diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/public/app.js b/project/短视频脚本创作/V1.0/mcn-work-shop/public/app.js index fc7db19..6a68ab9 100644 --- a/project/短视频脚本创作/V1.0/mcn-work-shop/public/app.js +++ b/project/短视频脚本创作/V1.0/mcn-work-shop/public/app.js @@ -43,7 +43,7 @@ async function executeTask(opts) { const d = await api('/api/run', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ prompt: opts.prompt, name: opts.label || '工作台任务' }), + body: JSON.stringify({ prompt: opts.prompt, name: opts.label || '工作台任务', skills: opts.skills }), }); if (d.error) throw new Error(d.error); taskId = d.id; 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 e918e42..caaa77f 100644 --- a/project/短视频脚本创作/V1.0/mcn-work-shop/public/data-pages.js +++ b/project/短视频脚本创作/V1.0/mcn-work-shop/public/data-pages.js @@ -206,7 +206,7 @@ async function confirmAiScript(accountName, btn) { const prompt = topic ? `请为达人「${accountName}」创作一条短视频脚本。${personaBlock}\n选题方向:${topic}\n${act}` : `请为达人「${accountName}」创作一条短视频脚本。${personaBlock}\n${act}`; - executeTask({ prompt, label: '创作指令', btn }); + executeTask({ prompt, label: '创作指令', btn, skills: ['短视频脚本创作'] }); }); } @@ -507,6 +507,7 @@ async function renderAccountDetail(view, id) { ? `${SKILL_HINT_CREATE}\n请为达人「${r.name}」创作一条全新的短视频故事脚本。${personaBlock}${noPersonaNote}${tb.block}${createTail}` : `${SKILL_HINT_CREATE}\n请为达人「${r.name}」创作一条全新的短视频故事脚本。${personaBlock}${noPersonaNote}\n该视频尚未解析,请先解析这个抖音视频并做拆解分析(${r.url ? '视频地址:' + r.url + '\n' : ''}视频标题:《${r.title}》),再基于账号设定与提炼出的选题,创作完整故事脚本。`, label: '创作指令', + skills: ['短视频脚本创作'], btn: b, }); })); @@ -517,6 +518,7 @@ async function renderAccountDetail(view, id) { target: acc.account_name, prompt: `请为达人「${acc.account_name}」提炼账号设定(生成人设卡)。`, label: '提炼指令', + skills: ['mcn-dou-analysis'], btn: exBtn, })); view._reload = load; // 静默自动刷新(保留当前 tab/翻页) @@ -647,6 +649,7 @@ async function renderRewrites(view) { ? `${SKILL_HINT_CREATE}\n请为达人「${r.name}」创作一条全新的短视频故事脚本。${personaBlock}${noPersonaNote}${tb.block}${createTail}` : `${SKILL_HINT_CREATE}\n请为达人「${r.name}」创作一条全新的短视频故事脚本。${personaBlock}${noPersonaNote}\n该视频尚未解析,请先解析这个抖音视频并做拆解分析(${r.url ? '视频地址:' + r.url + '\n' : ''}视频标题:《${r.title}》),再基于账号设定与提炼出的选题,创作完整故事脚本。`, label: '创作指令', + skills: ['短视频脚本创作'], btn: b, }); })); @@ -723,6 +726,7 @@ async function renderRewrites(view) { target: `《${title}》`, prompt: `请将视频《${title}》的 AI 脚本${regenerate ? '重新' : ''}展开成 AI 分镜提示词(按分镜技能生成镜头级描述)。`, label: '分镜指令', + skills: ['短视频分镜脚本'], btn, }); }; @@ -823,6 +827,7 @@ async function renderReviews(view) { desc: '将对该 AI 脚本做复盘评分(AI脚本 / 原视频脚本 / 对比分析三种模式),按 mcn-script-review(脚本复盘)技能执行。', prompt: '请对这条 AI 脚本做复盘评分(AI脚本 / 原视频脚本 / 对比分析三种模式),按 mcn-script-review(脚本复盘)技能执行。', label: '复盘指令', + skills: ['脚本复盘'], btn: b, }); })); diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/server.js b/project/短视频脚本创作/V1.0/mcn-work-shop/server.js index da84adc..8ddf726 100644 --- a/project/短视频脚本创作/V1.0/mcn-work-shop/server.js +++ b/project/短视频脚本创作/V1.0/mcn-work-shop/server.js @@ -505,8 +505,11 @@ const server = http.createServer((req, res) => { req.on('data', (c) => { body += c; if (body.length > 1e6) req.destroy(); }); req.on('end', () => { try { - const { prompt, name } = JSON.parse(body || '{}'); + const { prompt, name, skills } = JSON.parse(body || '{}'); if (!prompt || typeof prompt !== 'string' || !prompt.trim()) return sendErr(400, 'prompt 不能为空'); + // 09-01 参数级技能挂载:skills_json 是 automations 表原生字段(default '[]'), + // 客户端调度器创建会话时按该字段挂载技能;前端传技能名数组(如 ['短视频脚本创作'])即完成任务级技能绑定 + const skillsArr = Array.isArray(skills) ? skills.filter((s) => typeof s === 'string' && s.trim()).map((s) => s.trim()) : []; // 09-01 并发方案A:浏览器类任务(账号信息/视频列表/采集/导入)自动注入浏览器锁约束—— // 多个 AI 会话任务并行共享同一 9223 Chrome 实例,必须互斥使用,否则标签页互相导航抢占 const BROWSER_HINT = /账号信息|视频列表|保存并分析|采集|导入账号|网页采集|浏览器/; @@ -535,7 +538,7 @@ const server = http.createServer((req, res) => { const ownerUserId = uidRow ? uidRow.user_id : ''; db.prepare(`INSERT INTO automations (id,name,prompt,status,schedule_type,scheduled_at,next_run_at,rrule,cwds,created_at,updated_at,skills_json,connector_ids_json,model_id,permission_mode,owner_user_id,owner_status) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`) - .run(id, name || '工作台任务', finalPrompt, 'ACTIVE', 'once', scheduledAt, nextRunAt, '', JSON.stringify([cwd]), now, now, '[]', '[]', 'deepseek-v4-flash', 'fullAccess', ownerUserId, 'confirmed'); + .run(id, name || '工作台任务', finalPrompt, 'ACTIVE', 'once', scheduledAt, nextRunAt, '', JSON.stringify([cwd]), now, now, JSON.stringify(skillsArr), '[]', 'deepseek-v4-flash', 'fullAccess', ownerUserId, 'confirmed'); db.close(); log('已提交 AI 任务: ' + id + (name ? ' (' + name + ')' : '')); return sendJSON(200, { ok: true, id, message: '任务已提交,请在左侧会话栏查看执行' }); diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/submit-review-tasks.cjs b/project/短视频脚本创作/V1.0/mcn-work-shop/submit-review-tasks.cjs new file mode 100644 index 0000000..c235438 --- /dev/null +++ b/project/短视频脚本创作/V1.0/mcn-work-shop/submit-review-tasks.cjs @@ -0,0 +1,58 @@ +/* submit-review-tasks.cjs — 对俊希最新的 AI 脚本逐条提交复盘任务(与诊断页「复盘」按钮一致 + 落库 script_review 指令) + 用法: node submit-review-tasks.cjs [rewriteId1,rewriteId2,...] (不传则自动取 account_id=3 的最新 done 记录) */ +const { DatabaseSync } = require('node:sqlite'); +const path = require('path'); + +const DB = path.join(__dirname, 'mcn-plugin.db'); +const API = 'http://localhost:8900/api/run'; +const ACCOUNT_ID = 3; + +const db = new DatabaseSync(DB, { readOnly: true }); +let ids = process.argv.slice(2).map(Number).filter(Boolean); +if (!ids.length) { + ids = db.prepare("SELECT id FROM rewrite_log WHERE account_id=? AND status='done' AND script_text IS NOT NULL AND script_text <> '' ORDER BY id DESC LIMIT 20").all(ACCOUNT_ID).map((r) => r.id); +} +const rows = []; +for (const id of ids) { + const r = db.prepare('SELECT rl.id, rl.video_id, rl.title, v.video_title FROM rewrite_log rl LEFT JOIN account_videos v ON v.id=rl.video_id WHERE rl.id=?').get(id); + if (r) rows.push(r); +} +db.close(); +if (!rows.length) { console.log('NO REWRITES TO REVIEW'); process.exit(1); } + +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); +(async () => { + for (const r of rows) { + const prompt = `请对这条 AI 脚本做复盘评分(AI脚本 / 原视频脚本 / 对比分析三种模式),按 mcn-script-review(脚本复盘)技能执行。 +脚本标题:《${r.title || r.video_title || ''}》(rewrite_log.id=${r.id},video_id=${r.video_id}) + +【任务完成后必须执行】 +将复盘结果保存到工作台数据库 mcn-plugin.db 的 script_review 表(数据库路径:D:\\AgentSkill\\mcn-video-script\\project\\短视频脚本创作\\V1.0\\mcn-work-shop\\mcn-plugin.db),字段: +- type='ai' +- rewrite_id=${r.id} +- video_id=${r.video_id} +- dims_json=各维度评分 JSON(设定承接/框架节奏/钩子设置/叙事逻辑/极致事件/共鸣和共情/格式规范性/台词口语化/内容合规/教程完整链/其他) +- total=总分(数字) +- verdict=结论 +- issues_json=问题清单 JSON +- content=复盘完整内容 +- summary=摘要 +- status='done' +(用 node:sqlite 或 sqlite3 写入;若该表不存在则创建后再写) +完成后回复:评分 + 是否已保存。`; + + try { + const resp = await fetch(API, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ prompt, name: '复盘指令', skills: ['脚本复盘'] }), + }); + const d = await resp.json(); + console.log(`SUBMIT review rw${r.id} →`, d.ok ? ('id=' + d.id) : ('ERR ' + d.error)); + } catch (e) { + console.log(`SUBMIT review rw${r.id} → EXCEPTION ${e.message}`); + } + await sleep(3000); + } + console.log('ALL REVIEW TASKS SUBMITTED'); +})();