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:
maogeigei committed 2026-09-10 18:28:42 +08:00
1 parent 6cb29a9877
commit c48902f063
58 files changed
+895 -263

No files matched your search

@@ -1,7 +1,8 @@
// 短视频工作台 - 数据访问层(只读复用业务数据目录)
// 数据源:工作台目录 mcn-plugin.db(dsh 库副本,node:sqlite 只读)+ 三方数据目录 抖音榜单/*.json
// 与 dsh 原库(~/.dsh/mcn-plugin.db)隔离:两边各访问各的,互不影响
// 页面逻辑参照 dsh-plugin-mcn 重新开发,仅复用数据库 schema 与榜单文件格式
// 数据源:工作台目录 mcn-plugin.db(node:sqlite 只读)+ 三方数据目录 抖音榜单/*.json
// 09-10:dsh 侧已整套下线(~/.dsh/skills/、~/.dsh/mcn-plugin.db、D:\dshworkspace 均不存在),
// 工作台 DB 为唯一读写副本,不再有「与 dsh 原库隔离」的双库关系
// 页面逻辑参照 dsh-plugin-mcn 重新开发(历史沿革),仅复用数据库 schema 与榜单文件格式
'use strict';
const { DatabaseSync } = require('node:sqlite');
@@ -9,18 +10,19 @@ const path = require('node:path');
const fs = require('node:fs');
const os = require('node:os');
// 工作台自有数据库副本(与 dsh 原库隔离)
const DSH_DB = path.join(__dirname, 'mcn-plugin.db');
// 读取工作台 config.json(load-config.js 唯一入口 + 默认值唯一来源,配置收敛)
const { loadConfig } = require('./load-config');
const CFG = loadConfig();
// 榜单目录解析(09-03 定稿:跟随 mcn-data-insight 榜单落盘规范的数据源分层)
// 优先级:① 环境变量 MCN_RANKING_DIR 显式覆盖 → ② dsh 部署(技能在 ~/.dsh 下)→ D:\dshworkspace\抖音榜单
// → ③ 开发机/用户环境(①③):桌面 MCNSkill项目\三方数据\红狐数据\抖音榜单(运行时解析真实桌面,兼容 OneDrive 重定向)
// 工作台自有数据库副本(唯一读写副本)
const DSH_DB = path.join(__dirname, CFG.dbName);
// 榜单目录解析(09-03 定稿)
// 优先级:① 环境变量 MCN_RANKING_DIR 显式覆盖 → ② 桌面/<CFG.businessDataDir>(config.json,运行时解析真实桌面,兼容 OneDrive 重定向)
function resolveRankingDir() {
if (process.env.MCN_RANKING_DIR) return process.env.MCN_RANKING_DIR;
const isDshDeploy = __dirname.includes('.dsh');
if (isDshDeploy) return 'D:\\dshworkspace\\抖音榜单';
const home = os.homedir();
const rel = path.join('MCNSkill项目', '三方数据', '红狐数据', '抖音榜单');
const rel = path.join(...CFG.businessDataDir);
const candidates = [
path.join(home, 'Desktop', rel),
path.join(home, 'OneDrive', 'Desktop', rel),
@@ -295,8 +297,14 @@ function listRewrites(videoId) {
return { items: rows };
}
function listStoryboards(videoId) {
/* 09-10:原创选题(video_id 为空)原本查不到分镜 → 分镜 tab 无生成入口。
现支持按 rewrite_id 查询,使无视频载体的脚本也能挂分镜(storyboard_log 本就有 rewrite_id 列)。 */
function listStoryboards(videoId, rewriteId) {
const d = getDb(); if (!d) return [];
const rid = Number(rewriteId);
if (Number.isFinite(rid) && rid > 0 && !(Number(videoId) > 0)) {
return d.prepare(`SELECT id, video_id, rewrite_id, script_text, status, created_time, updated_time FROM storyboard_log WHERE rewrite_id=? ORDER BY id DESC`).all(rid);
}
return d.prepare(`SELECT id, video_id, rewrite_id, script_text, status, created_time, updated_time FROM storyboard_log WHERE video_id=? ORDER BY id DESC`).all(videoId);
}
@@ -428,12 +436,30 @@ function collectCategories(board, files) {
return RANK_KEEP.filter((c) => found.has(c));
}
// 期次真实区间:{ date: {start, end} }(周榜=周一~周日;日榜 start=end=当日)
// 09-10:UI 改显示「08-31 ~ 09-06」而非裸期号 2026-08-31——期号是周一,只看期号会误以为仅 8/31 单日。
// 同一 date 可能有多个文件(week 每赛道一个),取首个即可(区间相同)。
function collectRanges(board, files) {
const out = {};
const seen = new Set();
for (const pf of files) {
if (seen.has(pf.date)) continue;
seen.add(pf.date);
try {
const d = JSON.parse(fs.readFileSync(rankFilePath(pf.dir, pf.name), 'utf8'));
const s = String(d.dateStart || d.date || '').trim();
if (s) out[pf.date] = { start: s, end: String(d.dateEnd || d.date || s).trim() };
} catch (e) {}
}
return out;
}
function rankingMeta(board = 'week') {
const empty = { dates: [], categories: [] };
const empty = { dates: [], categories: [], ranges: {} };
const files = listRankFiles(board);
if (!files.length) return empty;
const dates = [...new Set(files.map((f) => f.date))];
return { dates, categories: collectCategories(board, files) };
return { dates, categories: collectCategories(board, files), ranges: collectRanges(board, files) };
}
// 榜单数据新鲜度(更新防重依据,09-03 新增):递归扫描 RANKING_DIR(三榜子目录 + 根目录旧平铺)全部 .json,
@@ -472,7 +498,7 @@ function parseNum(v) {
}
function getRanking({ board = 'week', date = '', category = '', sortBy = '', order = '', page = 1, pageSize = 20 } = {}) {
const empty = { dates: [], categories: [], category: '', date: '', total: 0, items: [] };
const empty = { dates: [], categories: [], ranges: {}, category: '', date: '', total: 0, items: [] };
const files = listRankFiles(board);
if (!files.length) return empty;
const dates = [...new Set(files.map((f) => f.date))];
@@ -524,7 +550,7 @@ function getRanking({ board = 'week', date = '', category = '', sortBy = '', ord
}
return { ...row, inDb: !!hit, dbId: hit ? hit.id : null };
});
return { dates, categories, category, date: selDate, total, page: Math.max(1, Number(page) || 1), pageSize: limit, items: pageItems };
return { dates, categories, ranges: collectRanges(board, files), category, date: selDate, total, page: Math.max(1, Number(page) || 1), pageSize: limit, items: pageItems };
}
/* ================= AI写脚本:原创选题(无视频载体)================= */