@@ -800,7 +804,7 @@ async function renderRanking(view) {
if (!m.ok) { view.innerHTML = `
${toolbar(st[tab], false)}
${esc(m.error || '加载失败')}
`; bindShell(); return null; }
metas[tab] = m;
const s = st[tab];
- s.dates = m.dates || []; s.categories = m.categories || [];
+ s.dates = m.dates || []; s.categories = m.categories || []; s.ranges = m.ranges || {};
if (!s.date) { s.date = m.date || (s.dates[0] || ''); s.category = m.category || ''; }
return m;
}
@@ -2005,9 +2009,9 @@ async function renderReviews(view) {
}
const [desc, prompt] = hasSource
? ['将对该 AI 脚本做复盘评分(AI脚本 / 原视频脚本 / 对比分析三种模式)。',
- `${SKILL_HINT_REVIEW}\n请对这条 AI 脚本做复盘评分(AI脚本 / 原视频脚本 / 对比分析三种模式)。`]
+ `${SKILL_HINT_REVIEW}\n请对这条 AI 脚本做复盘评分(AI脚本 / 原视频脚本 / 对比分析三种模式)。\n${REVIEW_ISSUE_RULE}`]
: ['将对该 AI 脚本做质量复盘评分(该脚本无原视频脚本,仅评 AI 脚本:独立分析模式·10 维评分制)。',
- `${SKILL_HINT_REVIEW}\n请对这条 AI 脚本做质量复盘评分(独立分析模式·10 维评分制)。该脚本无原视频脚本,只评 AI 脚本本身,不做源脚本复盘、不做对比分析。`];
+ `${SKILL_HINT_REVIEW}\n请对这条 AI 脚本做质量复盘评分(独立分析模式·10 维评分制)。该脚本无原视频脚本,只评 AI 脚本本身,不做源脚本复盘、不做对比分析。\n${REVIEW_ISSUE_RULE}`];
confirmRun({
title: hasSource ? '复盘评分' : 'AI脚本复盘',
desc,
@@ -2039,7 +2043,7 @@ async function renderReviews(view) {
body = `
总分 ${fmtScore(Number(rv.total))}
${scoreBar(dimsObj)}
`;
}
let issues = []; try { issues = JSON.parse(rv.issues_json || '[]'); } catch (e) {}
- if (issues.length) body += `
问题清单
${issues.map((it) => `
${esc(it.severity || '低')}${esc(it.issue || '')}${it.suggestion ? `→ ${esc(it.suggestion)}` : ''}
`).join('')}
`;
+ if (issues.length) body += `
问题清单
${issues.map(issueRowHtml).join('')}
`;
if (rv.summary) body += `
综合评语:${esc(rv.summary)}
`;
if (rv.content) body += `
${renderMarkdown(rv.content)}
`;
const mask = document.createElement('div'); mask.className = 'modal-mask';
@@ -2057,7 +2061,7 @@ async function renderReviews(view) {
布局同 AI写脚本查看弹窗(rw-stack 横向列):源脚本(可折叠,有原视频时) | AI脚本(版本切换) | 右侧列
右侧列头部 tab:脚本诊断(默认) | 分镜提示词
—— 诊断内容 = 当前版本 AI 脚本最新复盘(script_review type=ai 优先;compare 取 AI 视角总分并带源对比分)
- —— 分镜内容 = 该视频下 storyboard_log 中属当前版本的记录 + 生成/重新生成入口(仅参考选题脚本;原创选题无视频载体不支持)
+ —— 分镜内容 = storyboard_log 中属当前版本的记录 + 生成/重新生成入口(09-10 起:原创选题无视频载体时改按 rewrite_id 关联,同样支持)
—— 数据源:/api/dsh/video-script + /api/dsh/video-detail(参考选题)/ /api/dsh/custom-script(原创选题)
+ /api/dsh/review-full?rewriteId=(当前版本诊断)+ /api/dsh/storyboards?videoId=(分镜) */
const DIAG_DIMS = ['创意看点', '设定承接', '框架节奏', '爆款开场', '叙事逻辑', '极致事件', '格式规范性', '台词口语化', '内容合规', '教程完整链', '其他'];
@@ -2071,6 +2075,32 @@ const diagVisibleDims = (dims) => (diagDimHas(dims, '教程完整链') ? DIAG_DI
const diagScoreColor = (v) => (v >= 75 ? '#3a9e6d' : v >= 60 ? '#d99a2b' : '#d9534f');
const diagFmtScore = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : '-');
+/* 问题清单字段归一化(09-10 修复):模型输出的 issues_json schema 不稳定——
+ 历史同时存在 {sev:'high', issue, fix} 与 {severity:'高', issue, suggestion} 两套写法,
+ 前端原按后者严格读取 → severity 取不到 → 严重度全部兜底成绿色(60 分却全绿),
+ 且 suggestion 取不到 → 修改建议整段不显示。此处统一归一化,兼容两种字段与中英取值。 */
+const normIssue = (it) => {
+ const o = it && typeof it === 'object' ? it : {};
+ const pick = (...ks) => { for (const k of ks) { if (o[k] != null && String(o[k]).trim() !== '') return String(o[k]).trim(); } return ''; };
+ const raw = pick('severity', 'sev', 'level', '严重度').toLowerCase();
+ return {
+ level: /高|high|^h$|p0|严重/.test(raw) ? '高' : /中|mid|medium|^m$|p1/.test(raw) ? '中' : '低',
+ text: pick('issue', 'desc', 'text', '问题'),
+ fix: pick('suggestion', 'fix', 'suggest', '建议', '修改建议'),
+ dim: pick('dim', 'dimension', '维度'),
+ loc: pick('loc', 'position', '定位'),
+ };
+};
+const issueLevelColor = (lv) => (lv === '高' ? '#d9534f' : lv === '中' ? '#d99a2b' : '#3a9e6d');
+/* 复盘落库 issues_json 的字段契约(09-10):作为 prompt 后缀发给模型,从源头避免 schema 漂移 */
+const REVIEW_ISSUE_RULE = '【落库字段要求】issues_json 必须是数组,每条固定为 {"severity":"高|中|低","dim":"维度名","loc":"定位(场次/时间码)","issue":"问题描述","suggestion":"修改建议","step":"环节(S5-S11)"};severity 只能取中文「高/中/低」,禁止写成英文 high/medium/low,也禁止改用 sev/level 等其它字段名——否则工作台无法按严重度着色(会全部显示成绿色),修改建议也不会显示。';
+/* 单条问题渲染:严重度(色) + [所属维度] + 问题描述 + →修改建议 */
+const issueRowHtml = (raw) => {
+ const it = normIssue(raw);
+ const dimTag = it.dim ? `
[${esc(it.dim)}]` : '';
+ return `
${esc(it.level)}${dimTag}${esc(it.text)}${it.fix ? `→ ${esc(it.fix)}` : ''}
`;
+};
+
/* 分镜文本渲染:按「段 N(起-止 秒)|标题」分块(继承原 AI写脚本查看弹窗逻辑;分镜时间戳分段格式) */
function renderSbBlocks(text) {
const blocks = String(text || '').split(/\n\s*---+\s*\n/).map((b) => b.trim()).filter(Boolean);
@@ -2101,7 +2131,7 @@ async function reportFullReview(reviewId) {
body = `
总分 ${diagFmtScore(Number(rv.total))}
${scoreBar(dimsObj)}
`;
}
let issues = []; try { issues = JSON.parse(rv.issues_json || '[]'); } catch (e) {}
- if (issues.length) body += `
问题清单
${issues.map((it) => `
${esc(it.severity || '低')}${esc(it.issue || '')}${it.suggestion ? `→ ${esc(it.suggestion)}` : ''}
`).join('')}
`;
+ if (issues.length) body += `
问题清单
${issues.map(issueRowHtml).join('')}
`;
if (rv.summary) body += `
综合评语:${esc(rv.summary)}
`;
if (rv.content) body += `
${renderMarkdown(rv.content)}
`;
const mask = document.createElement('div'); mask.className = 'modal-mask';
@@ -2148,8 +2178,11 @@ async function openScriptModal({ rewriteId, videoId } = {}) {
const hasSource = vid > 0 && !!source;
let stab = 'diag'; /* 右侧 tab:diag=脚本诊断(默认)/ sb=分镜提示词 */
let showSrc = false, sbIdx = -1, showSide = false; /* R41:showSide=右侧「脚本诊断|分镜提示词」面板是否展开(收起后只展示 AI 脚本);R50:默认收起=不显示复盘和分镜;R55:sbIdx=-1=未选分镜版本,sideContent 自动指向该 AI 版本的最新分镜(末位) */
- const stb = vid > 0 ? await api('/api/dsh/storyboards?videoId=' + vid).catch(() => ({ items: [] })) : { items: [] };
- const storyboards = ((stb && stb.items) || []).slice().reverse(); /* 同版本条:旧→新,末位=最新分镜 */
+ /* 09-10:原创选题(vid=0)改按 rewriteId 查分镜——原逻辑只按 videoId,导致无视频载体的脚本永远查不到分镜、也没有生成入口 */
+ const sbQueryOf = (curId) => (vid > 0 ? 'videoId=' + vid : 'rewriteId=' + encodeURIComponent(curId));
+ const fetchSb = async (curId) => ((await api('/api/dsh/storyboards?' + sbQueryOf(curId)).catch(() => ({ items: [] }))) || {}).items || [];
+ const stb = { items: await fetchSb((rewrites[verIdx] || {}).id) };
+ let storyboards = ((stb && stb.items) || []).slice().reverse(); /* 同版本条:旧→新,末位=最新分镜 */
const visibleSbOf = (curId) => storyboards.filter((s) => s.rewrite_id == null || String(s.rewrite_id) === String(curId));
/* 复盘任务触发:有源脚本 → 三模式(AI/源/对比);无源 → 独立分析仅评 AI 脚本(同脚本诊断页行按钮口径) */
@@ -2158,9 +2191,9 @@ async function openScriptModal({ rewriteId, videoId } = {}) {
try {
const [desc, prompt] = hasSource
? ['将对该 AI 脚本做复盘评分(AI脚本 / 原视频脚本 / 对比分析三种模式)。',
- `${SKILL_HINT_REVIEW}\n请对这条 AI 脚本做复盘评分(AI脚本 / 原视频脚本 / 对比分析三种模式)。`]
+ `${SKILL_HINT_REVIEW}\n请对这条 AI 脚本做复盘评分(AI脚本 / 原视频脚本 / 对比分析三种模式)。\n${REVIEW_ISSUE_RULE}`]
: ['将对该 AI 脚本做质量复盘评分(该脚本无原视频脚本,仅评 AI 脚本:独立分析模式·10 维评分制)。',
- `${SKILL_HINT_REVIEW}\n请对这条 AI 脚本做质量复盘评分(独立分析模式·10 维评分制)。该脚本无原视频脚本,只评 AI 脚本本身,不做源脚本复盘、不做对比分析。`];
+ `${SKILL_HINT_REVIEW}\n请对这条 AI 脚本做质量复盘评分(独立分析模式·10 维评分制)。该脚本无原视频脚本,只评 AI 脚本本身,不做源脚本复盘、不做对比分析。\n${REVIEW_ISSUE_RULE}`];
confirmRun({ title: hasSource ? '复盘评分' : 'AI脚本复盘', desc, prompt, label: '复盘指令', skills: ['脚本复盘'], btn });
} finally { delete btn.dataset.lock; }
};
@@ -2170,7 +2203,10 @@ async function openScriptModal({ rewriteId, videoId } = {}) {
title: regenerate ? '重新生成分镜' : '生成分镜',
desc: '将基于该 AI 脚本(AI 创作的故事脚本),展开成 AI 分镜提示词(按分镜技能生成镜头级描述)。',
target: `《${title}》`,
- prompt: `${SKILL_HINT_STORYBOARD}\n请将视频《${title}》的 AI 脚本${regenerate ? '重新' : ''}展开成 AI 分镜提示词(F4 生成分镜,镜头级描述)。`,
+ /* 09-10:补落库指令——此前 prompt 只让"产出分镜",未要求写 storyboard_log,
+ 入库全靠模型自由发挥(故仅 3 条记录、且原创选题因无 video_id 无从关联)。
+ 现明确 video_id / rewrite_id,原创选题(vid=0)靠 rewrite_id 关联即可挂分镜。 */
+ prompt: `${SKILL_HINT_STORYBOARD}\n请将视频《${title}》的 AI 脚本${regenerate ? '重新' : ''}展开成 AI 分镜提示词(F4 生成分镜,镜头级描述)。\n\n【任务完成后必须执行】将分镜提示词全文写入工作台数据库 storyboard_log 表(数据库在本会话 cwd 下,文件名 mcn-plugin.db),字段:video_id=${vid > 0 ? vid : 'null'}、rewrite_id=${(curOf() || {}).id || ''}、script_text=分镜提示词全文、status='done'。(用 node:sqlite 或 sqlite3 写入;表不存在则创建)`,
label: '分镜指令',
skills: ['短视频分镜脚本'],
btn,
@@ -2194,7 +2230,7 @@ async function openScriptModal({ rewriteId, videoId } = {}) {
const scoreBar = diagVisibleDims(dims).map((k) => { const v = diagDimVal(dims, k); const has = typeof v === 'number' && Number.isFinite(v); return `
${esc(k)}${has ? v : '—'} `; }).join('');
let html = `
AI 总分 ${diagFmtScore(totalFin ? total : Number(rv.total))}${rv.type === 'compare' && Number.isFinite(srcTotal) ? `(对比源脚本 ${diagFmtScore(srcTotal)})` : ''}${rv.verdict ? ` · ${esc(rv.verdict)}` : ''}
${scoreBar}
`;
let issues = []; try { issues = JSON.parse(rv.issues_json || '[]'); } catch (e) {}
- if (issues.length) html += `
问题清单
${issues.map((it) => `
${esc(it.severity || '低')}${esc(it.issue || '')}${it.suggestion ? `→ ${esc(it.suggestion)}` : ''}
`).join('')}
`;
+ if (issues.length) html += `
问题清单
${issues.map(issueRowHtml).join('')}
`;
if (rv.summary) html += `
综合评语:${esc(rv.summary)}
`;
html += `
`;
return html;
@@ -2283,12 +2319,10 @@ async function openScriptModal({ rewriteId, videoId } = {}) {
} else if (visibleSb.length) {
sideBody = sb ? renderSbBlocks(sb.script_text) : '' /* R51:空态提示清空——不再显示「(暂无分镜提示词)」 */;
sideFoot = verCtrl('s', sbIdx, visibleSb.length, '分镜', '重新生成', sb ? sb.created_time : '');
- } else if (vid > 0) {
+ } else {
+ /* 09-10:移除 vid>0 限制——原创选题(无视频载体)同样可生成分镜,分镜按 rewrite_id 关联 */
sideBody = '' /* R51:空态提示清空——不再显示「(暂无分镜提示词)」 */;
sideFoot = ``;
- } else {
- sideBody = '
该脚本为原创选题(无关联视频),暂不支持分镜提示词
';
- sideFoot = '';
}
return { sideBody, sideFoot };
}
@@ -2344,6 +2378,8 @@ async function openScriptModal({ rewriteId, videoId } = {}) {
[...sideSec.children].forEach((ch) => { if (ch.classList && ch.classList.contains('rw-sec-foot')) ch.remove(); });
if (bodyEl) bodyEl.innerHTML = '
加载中…
';
const token = ++sideToken;
+ /* 原创选题(vid=0)分镜按 rewrite_id 查,切版本必须重查,否则会显示上一版本的分镜 */
+ if (!(vid > 0)) storyboards = (await fetchSb(c.id)).slice().reverse();
const { sideBody, sideFoot } = await sideContent(c);
if (!mask.isConnected || token !== sideToken) return; /* 弹窗已关闭 / 期间又切了 tab → 丢弃过期结果 */
if (bodyEl) bodyEl.innerHTML = sideBody;
diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/public/index.html b/project/短视频脚本创作/V1.0/mcn-work-shop/public/index.html
index acfacac..56933d3 100644
--- a/project/短视频脚本创作/V1.0/mcn-work-shop/public/index.html
+++ b/project/短视频脚本创作/V1.0/mcn-work-shop/public/index.html
@@ -4,7 +4,7 @@
短视频工作台
-
+
@@ -41,8 +41,8 @@
-
+
-
+