09-10 工作台:配置收敛、复盘字段契约修复、原创选题分镜支持、榜单期号区间显示
配置收敛:
- 端口/DB名/产出根/榜单目录单源化到 mcn-work-shop/config.json,代码层统一经 load-config.js 读取
- 新增 get-port.js 供 start.bat 读端口(bat 内嵌 node -e 会被 cmd 截断,读到的是兜底值)
- start.bat 修复失效的 managed node 路径(原写死 22.22.2 已不存在),改为遍历 versions 自动探测
复盘诊断页(真 bug 修复):
- 问题清单严重度全部显示成绿色 + 修改建议不显示:模型输出 {sev:'high',fix} 与前端读取 {severity:'高',suggestion} 字段名与取值均不匹配
- 前端新增 normIssue 归一化,兼容 sev/severity/level 与中英取值,并回补所属维度显示
- 在 submit-review-tasks 与诊断/复盘 prompt 中固化 issues_json 字段契约,从源头防 schema 漂移
分镜提示词:
- 原创选题(video_id 为空)原本无生成入口(分镜按 video_id 查询)
- listStoryboards 支持按 rewrite_id 查询,前端放开入口、切版本重查,并补落库指令(此前入库靠模型自由发挥)
榜单展示:
- 期号改为显示区间「08/31 ~ 09/06」,避免被误读为单日(期号=该周周一)
- 后端新增 collectRanges,从落盘文件读真实 dateStart/dateEnd 随 API 返回(日榜 start=end 不显区间)
其他:
- 清理 dsh 环境废弃后的历史叙述类废话(16 文件约 18 处),文末变更记录区保留
- 新增 docs/工作台UI规范.md(设计 token / 布局 / 8 页面路由 / 12 类组件 / 9 条已知坑)
This commit is contained in:
1 parent
6cb29a9877
commit
c48902f063
58 files changed
+895
-263
No files matched your search
@@ -1,6 +1,6 @@
|
||||
// 短视频工作台 - 本地 Web 服务(零依赖 Node 实现)
|
||||
// 启动: node server.js [端口] → http://localhost:8900(默认;可传参指定端口,如 node server.js 9000)
|
||||
// 功能: 技能导航 + 产出内容浏览(默认读桌面 MCNSkill项目)+ dsh 数据功能页(账号/视频/脚本/复盘/周榜,只读)
|
||||
// 功能: 技能导航 + 产出内容浏览(默认读桌面 <outputRoot>,根目录名由 config.json 决定)+ dsh 数据功能页(账号/视频/脚本/复盘/周榜,只读)
|
||||
'use strict';
|
||||
|
||||
const http = require('http');
|
||||
@@ -11,14 +11,24 @@ const { execFileSync } = require('child_process');
|
||||
const { URL } = require('url');
|
||||
const dsh = require('./dsh-data');
|
||||
const { parseAttachment } = require('./scripts/attachment-parser');
|
||||
const { loadConfig } = require('./load-config');
|
||||
|
||||
// 09-01 默认端口由 8899 改为 8900:本机 8899 被小米 PC 管家 MiPCAudio.exe 系统服务占用(0.0.0.0 监听且自动复活),
|
||||
// 此时 127.0.0.1:8899 绑定不生效 → 默认直接起在 8900;仍可 `node server.js <端口>` 显式换端口
|
||||
const PORT_BASE = parseInt(process.argv[2], 10) || 8900;
|
||||
const ROOT = __dirname;
|
||||
const PUBLIC_DIR = path.join(ROOT, 'public');
|
||||
const CONFIG_FILE = path.join(ROOT, 'config.json');
|
||||
|
||||
// 09-10 配置收敛:工作台设定(端口/DB名/产出根/榜单目录)统一收进 config.json,代码层一律从 CFG 读,
|
||||
// 避免调整时到处改(config 缺失/损坏时回退 load-config.cjs 默认值)。argv 端口优先级仍高于 config。
|
||||
const CFG = loadConfig();
|
||||
const PORT_BASE = parseInt(process.argv[2], 10) || CFG.port;
|
||||
|
||||
// 09-10 路径治本(配套「仓库特征判定」):工作台运行时路径一律从 ROOT 动态推导,禁止写死盘符。
|
||||
// 根因:原写死盘符指向仓库外的固定目录,仓库迁移后该目录不存在 → 浏览器锁失效、会话 cwd 悬空。
|
||||
const BROWSER_LOCK_FILE = path.join(ROOT, '.browser-lock'); // 浏览器互斥锁文件(与工作台同目录)
|
||||
const SESSION_CWD = ROOT; // 工作台触发的 AI 会话 cwd(分组名 = mcn-work-shop)
|
||||
|
||||
// 09-07:prompt 模板文件化(prompts/*.md)——规则迭代只改文件不碰代码;文件缺失/损坏回退 null,调用方自行兜底。
|
||||
// 模板正文取 ==PROMPT== 与 ==CHANGELOG== 之间(头部说明/文末变更记录不发给模型)
|
||||
const PROMPTS_DIR = path.join(ROOT, 'prompts');
|
||||
@@ -45,22 +55,14 @@ function loadPrompt(name) {
|
||||
} catch (e) { return null; }
|
||||
}
|
||||
|
||||
// ---- 默认根目录:桌面/MCNSkill项目 ----
|
||||
const DEFAULT_ROOT = path.join(os.homedir(), 'Desktop', 'MCNSkill项目');
|
||||
// ---- 默认根目录:桌面/<CFG.outputRoot> ----
|
||||
const DEFAULT_ROOT = path.join(os.homedir(), 'Desktop', CFG.outputRoot);
|
||||
|
||||
// ---- 配置读写(自定义根目录列表)----
|
||||
function loadConfig() {
|
||||
try {
|
||||
if (fs.existsSync(CONFIG_FILE)) {
|
||||
const c = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
||||
if (Array.isArray(c.extraRoots)) return { extraRoots: c.extraRoots };
|
||||
}
|
||||
} catch (e) { /* 忽略损坏配置 */ }
|
||||
return { extraRoots: [] };
|
||||
}
|
||||
|
||||
function saveConfig(cfg) {
|
||||
fs.writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2), 'utf8');
|
||||
// ---- 配置读写(自定义根目录列表;loadConfig 来自 load-config.js,每次读盘取最新值)----
|
||||
function saveConfig(patch) {
|
||||
// 合并写盘:保留 config.json 里的其它字段(port/dbName/outputRoot/businessDataDir),只更新传入的键
|
||||
const merged = Object.assign({}, loadConfig(), patch);
|
||||
fs.writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2), 'utf8');
|
||||
}
|
||||
|
||||
// ---- 允许读取的根目录白名单 ----
|
||||
@@ -338,7 +340,7 @@ function createOnceAutomation(name, prompt, skillsArr, connectorIdsArr) {
|
||||
try {
|
||||
const now = Date.now();
|
||||
const id = 'automation-' + now;
|
||||
const cwd = 'D:\\AgentSkill\\mcn-workshop'; // 工作台触发的会话归入 mcn-workshop 空间分组
|
||||
const cwd = SESSION_CWD; // 工作台触发的会话归入 mcn-work-shop 空间分组(由 ROOT 动态推导)
|
||||
const d = new Date(now + 5 * 1000);
|
||||
const pad = (n) => String(n).padStart(2, '0');
|
||||
const scheduledAt = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
||||
@@ -490,7 +492,7 @@ const server = http.createServer((req, res) => {
|
||||
|
||||
// ---------- 数据功能(只读工作台自有数据库副本) ----------
|
||||
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; };
|
||||
const dshGuard = (res2) => { if (!dsh.dbAvailable()) { sendJSON(200, { ok: false, error: '未检测到工作台数据库(' + CFG.dbName + '),本功能不可用' }); return true; } return false; };
|
||||
const qInt = (k, def) => { const v = Number(q.get(k)); return Number.isFinite(v) ? v : def; };
|
||||
|
||||
if (p === '/api/dsh/meta') {
|
||||
@@ -627,7 +629,7 @@ const server = http.createServer((req, res) => {
|
||||
}
|
||||
if (p === '/api/dsh/storyboards') {
|
||||
if (dshGuard()) return;
|
||||
return dshOk(200, { items: dsh.listStoryboards(qInt('videoId', 0)) });
|
||||
return dshOk(200, { items: dsh.listStoryboards(qInt('videoId', 0), qInt('rewriteId', 0)) });
|
||||
}
|
||||
if (p === '/api/dsh/review-detail') {
|
||||
if (dshGuard()) return;
|
||||
@@ -717,7 +719,7 @@ const server = http.createServer((req, res) => {
|
||||
// 09-01 并发方案A:浏览器类任务(账号信息/视频列表/采集/导入)自动注入浏览器锁约束——
|
||||
// 多个 AI 会话任务并行共享同一 9223 Chrome 实例,必须互斥使用,否则标签页互相导航抢占
|
||||
const BROWSER_HINT = /账号信息|视频列表|保存并分析|采集|导入账号|网页采集|浏览器/;
|
||||
const BROWSER_LOCK = 'D:\\\\AgentSkill\\\\mcn-workshop\\\\.browser-lock';
|
||||
const BROWSER_LOCK = BROWSER_LOCK_FILE;
|
||||
let finalPrompt = prompt.trim();
|
||||
if (BROWSER_HINT.test(finalPrompt)) {
|
||||
finalPrompt += `
|
||||
@@ -733,7 +735,7 @@ const server = http.createServer((req, res) => {
|
||||
const db = new DatabaseSync(process.env.WORKBUDDY_DB || path.join(os.homedir(), '.workbuddy', 'workbuddy.db'));
|
||||
const now = Date.now();
|
||||
const id = 'automation-' + now;
|
||||
const cwd = 'D:\\AgentSkill\\mcn-workshop'; // 工作台触发的会话归入 mcn-workshop 空间分组
|
||||
const cwd = SESSION_CWD; // 工作台触发的会话归入 mcn-work-shop 空间分组(由 ROOT 动态推导)
|
||||
const d = new Date(Date.now() + 5 * 1000); // 未来 5 秒(08-31 根因:客户端只对未来 scheduledAt 补算 next_run_at;写 now=过去时间→不补算→调度器扫不到→卡死)
|
||||
const pad = (n) => String(n).padStart(2, '0');
|
||||
const scheduledAt = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
||||
@@ -798,7 +800,7 @@ const server = http.createServer((req, res) => {
|
||||
let contentText = '', personaText = '', videoRefText = '';
|
||||
try {
|
||||
const { DatabaseSync } = require('node:sqlite');
|
||||
const db = new DatabaseSync(path.join(ROOT, 'mcn-plugin.db'), { readOnly: true });
|
||||
const db = new DatabaseSync(path.join(ROOT, CFG.dbName), { readOnly: true });
|
||||
const acc = db.prepare('SELECT * FROM hot_accounts WHERE account_name=? LIMIT 1').get(accountName);
|
||||
if (acc) {
|
||||
contentText = String(acc.content || '').slice(0, 500);
|
||||
@@ -873,7 +875,7 @@ const server = http.createServer((req, res) => {
|
||||
let personaText = '', contentText = '';
|
||||
try {
|
||||
const { DatabaseSync } = require('node:sqlite');
|
||||
const db = new DatabaseSync(path.join(ROOT, 'mcn-plugin.db'), { readOnly: true });
|
||||
const db = new DatabaseSync(path.join(ROOT, CFG.dbName), { readOnly: true });
|
||||
const acc = db.prepare('SELECT * FROM hot_accounts WHERE account_name=? LIMIT 1').get(accountName);
|
||||
if (acc) {
|
||||
contentText = String(acc.content || '').slice(0, 300);
|
||||
@@ -1019,7 +1021,7 @@ const server = http.createServer((req, res) => {
|
||||
const accountDir = resolveAccountDir(account, root);
|
||||
if (!accountDir) return sendErr(404, '账号产出目录不存在: ' + account);
|
||||
const { DatabaseSync } = require('node:sqlite');
|
||||
const db = new DatabaseSync(path.join(ROOT, 'mcn-plugin.db'));
|
||||
const db = new DatabaseSync(path.join(ROOT, CFG.dbName));
|
||||
const res = importAccountIntoDb(db, account, accountDir);
|
||||
db.close();
|
||||
log('分析产物入库: ' + account + ' → ' + JSON.stringify(res));
|
||||
@@ -1042,12 +1044,14 @@ const server = http.createServer((req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
// ---- 会话自动清理:mcn-workshop 空间超 6 小时无活动的已结束会话自动清除 ----
|
||||
// 09-01 用户需求:工作台触发(cwd=D:\AgentSkill\mcn-workshop)的 AI 会话任务,超过 6 小时自动清除
|
||||
// 判定:cwd 含 mcn-workshop + status=completed(执行中一律不删)+ 未删过 + 最后活动时间超过 6 小时
|
||||
// ---- 会话自动清理:mcn-work-shop 空间超 6 小时无活动的已结束会话自动清除 ----
|
||||
// 09-01 用户需求:工作台触发(cwd = 工作台目录 SESSION_CWD)的 AI 会话任务,超过 6 小时自动清除
|
||||
// 09-10 路径治本:cwd 由写死盘符改为 ROOT 动态推导(<技能根>/mcn-work-shop);
|
||||
// 清理模式放宽为 '%mcn-work%',同时覆盖历史会话(旧 mcn-workshop)与新建会话(mcn-work-shop)
|
||||
// 判定:cwd 含 mcn-work + status=completed(执行中一律不删)+ 未删过 + 最后活动时间超过 6 小时
|
||||
// 方式:软删除(置 deleted_at),客户端左侧会话栏按 deleted_at 过滤即不再显示;可逆安全
|
||||
const SESSION_TTL_MS = 6 * 3600 * 1000; // 6 小时
|
||||
const SESSION_CWD_PATTERN = '%mcn-workshop%'; // 与 /api/run 写入的 cwd 一致
|
||||
const SESSION_CWD_PATTERN = '%mcn-work%'; // 兼容 SESSION_CWD(mcn-work-shop)与历史 cwd(mcn-workshop)
|
||||
const SESSION_CLEAN_INTERVAL_MS = 60 * 60 * 1000; // 每小时检查一次
|
||||
|
||||
function cleanupExpiredSessions() {
|
||||
@@ -1064,7 +1068,7 @@ function cleanupExpiredSessions() {
|
||||
AND COALESCE(last_activity_at, updated_at, created_at, 0) < ?
|
||||
`).run(now, SESSION_CWD_PATTERN, cutoff);
|
||||
db.close();
|
||||
if (Number(r.changes) > 0) log(`会话自动清理:已清除 mcn-workshop 超 6 小时无活动的已完成会话 ${r.changes} 条`);
|
||||
if (Number(r.changes) > 0) log(`会话自动清理:已清除 mcn-work-shop 超 6 小时无活动的已完成会话 ${r.changes} 条`);
|
||||
} catch (e) {
|
||||
console.error('会话自动清理失败:', e.message);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user