diff --git a/modules/ena-planner/ena-planner.css b/modules/ena-planner/ena-planner.css index e819880..52dec9c 100644 --- a/modules/ena-planner/ena-planner.css +++ b/modules/ena-planner/ena-planner.css @@ -618,6 +618,50 @@ textarea.input { font-size: .8125rem; } +/* Message cards inside log */ +.msg-list { + display: flex; + flex-direction: column; + gap: 8px; + margin-top: 8px; +} + +.msg-card { + border-radius: var(--radius); + border-left: 3px solid var(--bdr); + background: var(--code-bg); + padding: 8px 12px; +} + +.msg-card.msg-system { border-left-color: #6b8afd; } +.msg-card.msg-user { border-left-color: #4ecdc4; } +.msg-card.msg-assistant { border-left-color: #f7a046; } + +.msg-role { + font-size: .6875rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: .04em; + margin-bottom: 4px; + color: var(--txt3); +} + +.msg-system .msg-role { color: #6b8afd; } +.msg-user .msg-role { color: #4ecdc4; } +.msg-assistant .msg-role { color: #f7a046; } + +.msg-content { + font-family: 'SF Mono', Monaco, Consolas, 'Liberation Mono', monospace; + font-size: .6875rem; + line-height: 1.6; + white-space: pre-wrap; + word-break: break-word; + color: var(--code-txt); + margin: 0; + max-height: 300px; + overflow-y: auto; +} + details { margin-bottom: 6px; } @@ -841,4 +885,4 @@ details summary:hover { details summary { padding: 8px 0; } -} \ No newline at end of file +} diff --git a/modules/ena-planner/ena-planner.html b/modules/ena-planner/ena-planner.html index bb7309e..12e9f4b 100644 --- a/modules/ena-planner/ena-planner.html +++ b/modules/ena-planner/ena-planner.html @@ -16,7 +16,7 @@

EnaPlanner

-
Story Planning · LLM Integration
+
Story Planning · LLM Integration —— Created by Hao19911125
@@ -636,6 +636,23 @@ const time = item.time ? new Date(item.time).toLocaleString() : '-'; const cls = item.ok ? 'success' : 'error'; const label = item.ok ? '成功' : '失败'; + + // Format request messages: one card per message with role label + let msgHtml = ''; + if (Array.isArray(item.requestMessages) && item.requestMessages.length) { + msgHtml = item.requestMessages.map((m, i) => { + const role = escapeHtml(m.role || 'unknown'); + const roleClass = role === 'system' ? 'msg-system' : role === 'user' ? 'msg-user' : 'msg-assistant'; + const content = escapeHtml(m.content || ''); + return `
+
[${i + 1}] ${role}
+
${content}
+
`; + }).join(''); + } else { + msgHtml = '
无消息
'; + } + return `
@@ -643,8 +660,8 @@ ${escapeHtml(item.model || '-')}
${item.error ? `
${escapeHtml(item.error)}
` : ''} -
请求消息 -
${escapeHtml(JSON.stringify(item.requestMessages || [], null, 2))}
+
请求消息 (${(item.requestMessages || []).length} 条) +
${msgHtml}
原始回复
${escapeHtml(item.rawReply || '')}
diff --git a/modules/ena-planner/ena-planner.js b/modules/ena-planner/ena-planner.js index 565829d..db12f4f 100644 --- a/modules/ena-planner/ena-planner.js +++ b/modules/ena-planner/ena-planner.js @@ -7,6 +7,7 @@ import { extensionFolderPath } from '../../core/constants.js'; import { EnaPlannerStorage } from '../../core/server-storage.js'; import { postToIframe, isTrustedIframeEvent } from '../../core/iframe-messaging.js'; import { DEFAULT_PROMPT_BLOCKS, BUILTIN_TEMPLATES } from './ena-planner-presets.js'; +import { formatOutlinePrompt } from '../story-outline/story-outline.js'; const EXT_NAME = 'ena-planner'; const OVERLAY_ID = 'xiaobaix-ena-planner-overlay'; @@ -307,7 +308,8 @@ function formatCharCardBlock(charObj) { function cleanAiMessageText(text) { let out = String(text ?? ''); - // 1) Strip properly wrapped / blocks only. + // 1) Strip everything before and including (handles unclosed think blocks) + out = out.replace(/^[\s\S]*?<\/think>/i, ''); out = out.replace(/]*>[\s\S]*?<\/think>/gi, ''); out = out.replace(/]*>[\s\S]*?<\/thinking>/gi, ''); @@ -688,19 +690,51 @@ async function buildWorldbookBlock(scanText) { * -------------------------- */ function getChatVariables() { - // Try multiple paths to get ST chat variables + let vars = {}; + + // 1) Chat-level variables + try { + const ctx = getContextSafe(); + if (ctx?.chatMetadata?.variables) vars = { ...ctx.chatMetadata.variables }; + } catch {} + if (!Object.keys(vars).length) { try { - const ctx = getContextSafe(); - if (ctx?.chatMetadata?.variables) return ctx.chatMetadata.variables; - } catch { } + if (window.chat_metadata?.variables) vars = { ...window.chat_metadata.variables }; + } catch {} + } + if (!Object.keys(vars).length) { try { - if (window.chat_metadata?.variables) return window.chat_metadata.variables; - } catch { } - try { - const ctx = getContextSafe(); - if (ctx?.chat_metadata?.variables) return ctx.chat_metadata.variables; - } catch { } - return {}; + const ctx = getContextSafe(); + if (ctx?.chat_metadata?.variables) vars = { ...ctx.chat_metadata.variables }; + } catch {} + } + + // 2) Always merge message-level variables (some presets store vars here instead of chat-level) + try { + const msgVars = getLatestMessageVarTable(); + if (msgVars && typeof msgVars === 'object') { + for (const key of Object.keys(msgVars)) { + // Skip MVU internal metadata keys + if (key === 'schema' || key === 'display_data' || key === 'delta_data') continue; + if (vars[key] === undefined) { + // Chat-level doesn't have this key at all — take from message-level + vars[key] = msgVars[key]; + } else if ( + vars[key] && typeof vars[key] === 'object' && !Array.isArray(vars[key]) && + msgVars[key] && typeof msgVars[key] === 'object' && !Array.isArray(msgVars[key]) + ) { + // Both have this key as objects — shallow merge (message-level fills gaps) + for (const subKey of Object.keys(msgVars[key])) { + if (vars[key][subKey] === undefined) { + vars[key][subKey] = msgVars[key][subKey]; + } + } + } + } + } + } catch {} + + return vars; } function buildEjsContext() { @@ -735,6 +769,7 @@ function buildEjsContext() { return { getvar, setvar, + vars, Number, Math, JSON, String, Array, Object, parseInt, parseFloat, console: { log: () => { }, warn: () => { }, error: () => { } }, }; @@ -1101,6 +1136,7 @@ async function buildPlannerMessages(rawUserInput) { const scanText = [charBlockRaw, cachedSummary, recentChatRaw, vectorRaw, plotsRaw, rawUserInput].join('\n\n'); const worldbookRaw = await buildWorldbookBlock(scanText); + const outlineRaw = typeof formatOutlinePrompt === 'function' ? (formatOutlinePrompt() || '') : ''; // Render templates/macros const charBlock = await renderTemplateAll(charBlockRaw, env, messageVars); @@ -1110,6 +1146,7 @@ async function buildPlannerMessages(rawUserInput) { const storySummary = cachedSummary.trim().length > 30 ? await renderTemplateAll(cachedSummary, env, messageVars) : ''; const worldbook = await renderTemplateAll(worldbookRaw, env, messageVars); const userInput = await renderTemplateAll(rawUserInput, env, messageVars); + const storyOutline = outlineRaw.trim().length > 10 ? await renderTemplateAll(outlineRaw, env, messageVars) : ''; const messages = []; @@ -1125,6 +1162,11 @@ async function buildPlannerMessages(rawUserInput) { // 3) Worldbook if (String(worldbook).trim()) messages.push({ role: 'system', content: worldbook }); + // 3.5) Story Outline / 剧情地图(小白板世界架构) + if (storyOutline.trim()) { + messages.push({ role: 'system', content: `\n${storyOutline}\n` }); + } + // 4) Chat history (last 2 AI responses — floors N-1 & N-3) if (String(recentChat).trim()) messages.push({ role: 'system', content: recentChat }); diff --git a/modules/story-outline/story-outline.js b/modules/story-outline/story-outline.js index aa01a92..3a0a08c 100644 --- a/modules/story-outline/story-outline.js +++ b/modules/story-outline/story-outline.js @@ -1395,4 +1395,4 @@ jQuery(() => { window.registerModuleCleanup?.('storyOutline', cleanup); }); -export { cleanup }; +export { cleanup, formatOutlinePrompt };