From 57e5e17c5aa5a565ed1503f1fd3326576347c5d9 Mon Sep 17 00:00:00 2001 From: Hao19911125 <99091644+Hao19911125@users.noreply.github.com> Date: Wed, 18 Feb 2026 22:09:33 +0800 Subject: [PATCH 01/26] Update story-summary.js --- modules/story-summary/story-summary.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/story-summary/story-summary.js b/modules/story-summary/story-summary.js index 4f95d0b..d5e3ec4 100644 --- a/modules/story-summary/story-summary.js +++ b/modules/story-summary/story-summary.js @@ -116,6 +116,7 @@ let events = null; let activeChatId = null; let vectorCancelled = false; let vectorAbortController = null; +let _lastBuiltPromptText = ""; // ═══════════════════════════════════════════════════════════════════════════ // TaskGuard — 互斥任务管理(summary / vector / anchor) @@ -1667,7 +1668,7 @@ async function handleGenerationStarted(type, _params, isDryRun) { const cfg = getSummaryPanelConfig(); const roleKey = cfg.trigger?.role || 'system'; const role = ROLE_MAP[roleKey] || extension_prompt_roles.SYSTEM; - + _lastBuiltPromptText = text; // 写入 extension_prompts extension_prompts[EXT_PROMPT_KEY] = { value: text, @@ -1781,3 +1782,6 @@ jQuery(() => { maybePreloadTokenizer(); }); +export function getStorySummaryForEna() { + return _lastBuiltPromptText; +} From e81abdac69aeeb6e5f53447b75b9414c5ea0811b Mon Sep 17 00:00:00 2001 From: Hao19911125 <99091644+Hao19911125@users.noreply.github.com> Date: Wed, 18 Feb 2026 22:13:10 +0800 Subject: [PATCH 02/26] Update settings.html --- settings.html | 513 +++++++++++++++++++++++++------------------------- 1 file changed, 260 insertions(+), 253 deletions(-) diff --git a/settings.html b/settings.html index 2e471de..d0096cc 100644 --- a/settings.html +++ b/settings.html @@ -1,237 +1,243 @@ -
-
- 小白X -
-
-
-
-
- - - - -
-
-
-
总开关
-
-
- - - -
-
- - - - -
-
渲染模式 -
-
-
- - -
-
- - -
-
- - -
-
- - -
-
-
流式,非基础的渲染 -
-
-
- - -
-
-
-
当前角色模板设置
-
- -
-
-
- 请选择一个角色 -
-
-
功能说明 -
-
-
- - - -
-
- - - -
-
-
-
+
+
+ 小白X +
+
+
+
+
+ + + + + +
+
+
+
总开关
+
+
+ + + +
+
+ + + + +
+
渲染模式 +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
流式,非基础的渲染 +
+
+
+ + +
+
+
+
当前角色模板设置
+
+ +
+
+
+ 请选择一个角色 +
+
+
功能说明 +
+
+
+ + + +
+
+ + + + +
+
+
+
+
+
+
+
Ena Planner Logs
+
+ + + +
+
+
+
+
`; +} + +function renderLogs() { + const body = document.getElementById('ep_log_body'); + if (!body) return; + + if (!state.logs.length) { + body.innerHTML = `
暂无日志(发送一次消息后就会记录)。
`; + return; + } + + const html = state.logs.map((log, idx) => { + const t = log.time ?? ''; + const title = log.ok ? 'OK' : 'ERROR'; + const model = log.model ?? ''; + const err = log.error ?? ''; + + // Format request messages for readable display + const reqDisplay = (log.requestMessages ?? []).map((m, i) => { + return `--- Message #${i + 1} [${m.role}] ---\n${m.content ?? '(empty)'}`; + }).join('\n\n'); + + return ` +
+
+ #${idx + 1} · ${title} · ${t} · ${model} + ${log.ok ? '✅' : '❌'} +
+ ${err ? `
${escapeHtml(err)}
` : ''} + +
+ 发出去的 messages(完整) +
${escapeHtml(reqDisplay)}
+
+ +
+ 规划 AI 原始完整回复(含 <think>) +
${escapeHtml(String(log.rawReply ?? ''))}
+
+ +
+ 写回输入框的版本(已剔除 think,只保留 plot+note) +
${escapeHtml(String(log.filteredReply ?? ''))}
+
+
`; + }).join(''); + + body.innerHTML = html; +} + +function openLogModal() { + const m = document.getElementById('ep_log_modal'); + if (!m) return; + m.classList.add('open'); + renderLogs(); +} +function closeLogModal() { + const m = document.getElementById('ep_log_modal'); + if (!m) return; + m.classList.remove('open'); +} + +/** ------------------------- + * Settings UI — Issue #1: use inline-drawer for collapsible + * --------------------------*/ +function createSettingsHTML() { + const s = ensureSettings(); + const channel = s.api.channel; + + return ` +
+
+
+ Ena Planner + + + ${s.enabled ? 'Enabled' : 'Disabled'} + +
+
+
+ +
+
总览
+
API
+
提示词
+
调试
+
+ + +
+
+
+ + +
开启后:你点发送/回车,会先走"规划模型",把规划结果写回输入框再发送。
+
+ +
+ + +
防止"原始+规划文本"再次被拦截规划。
+
+
+ +
+ +
+
+ + +
角色绑定的世界书总是会读取。这里选择是否额外包含全局世界书。
+
+ +
+ + +
+
+ +
+
+ + +
条目名称/备注包含这些字符串的条目会被排除。
+
+
+ +
+
+ + +
+
+ +
+ 自动行为说明:
+ · 聊天片段:自动读取所有未隐藏的 AI 回复(不含用户输入)
+ · 自动剔除 <think> 以前的内容(含未包裹的思考段落)
+ · 角色卡字段(desc/personality/scenario):有就全部加入
+ · 向量召回(extensionPrompts):有就自动加入
+ · 世界书激活:常驻(蓝灯)+ 关键词触发(绿灯) +
+ +
+ +
+
+ + +
这些 XML 标签及其内容会从聊天历史中剔除。自闭合标签(如 <Tag/>)也会被移除。
+
+
+ +
+ + +
+
+ + +
+
+
+ + +
影响默认前缀:OpenAI/Claude → /v1,Gemini → /v1beta
+
+ +
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
新增多条提示词块,选择 role(system/user/assistant)。系统块放最前面;assistant 块放最后。
+ +
+ + + + +
+ +
+
+ + +
+
+ + +
+
+ 工作原理:
+ · 规划时会锁定发送按钮
+ · Log 静默记录,只有出错才弹提示
+ · 写回版本:剔除 <think>,只保留 <plot>+<note>
+ · 前文自动剔除 <think> 以前内容和排除标签内容 +
+
+ + +
+ +
+ +
+
+
`; +} + +function renderPromptDesigner() { + const s = ensureSettings(); + const list = document.getElementById('ep_prompt_list'); + if (!list) return; + + const blocks = s.promptBlocks || []; + const rows = blocks.map((b, idx) => { + const role = b.role || 'system'; + return ` +
+
+
+ + +
+
+ + + +
+
+ +
`; + }).join(''); + + list.innerHTML = rows || `
暂无提示词块
`; +} + +function bindSettingsUI() { + const settingsEl = document.getElementById('ena_planner_settings'); + if (!settingsEl) return; + + // Tabs + settingsEl.querySelectorAll('.ep-tab').forEach(tab => { + tab.addEventListener('click', () => { + settingsEl.querySelectorAll('.ep-tab').forEach(t => t.classList.remove('active')); + tab.classList.add('active'); + const id = tab.getAttribute('data-ep-tab'); + settingsEl.querySelectorAll('.ep-panel').forEach(p => p.classList.remove('active')); + const panel = settingsEl.querySelector(`.ep-panel[data-ep-panel="${id}"]`); + if (panel) panel.classList.add('active'); + if (id === 'prompt') renderPromptDesigner(); + }); + }); + + function save() { saveSettingsDebounced(); } + + // General + document.getElementById('ep_enabled')?.addEventListener('change', (e) => { + const s = ensureSettings(); + s.enabled = e.target.value === 'true'; + save(); + toastInfo(`Ena Planner: ${s.enabled ? '启用' : '关闭'}`); + // Update badge + const badge = document.querySelector('.ep-badge-inline'); + if (badge) { + badge.className = `ep-badge-inline ${s.enabled ? 'ok' : 'warn'}`; + badge.querySelector('span:last-child').textContent = s.enabled ? 'Enabled' : 'Disabled'; + } + }); + + document.getElementById('ep_skip_plot')?.addEventListener('change', (e) => { + ensureSettings().skipIfPlotPresent = e.target.value === 'true'; save(); + }); + + document.getElementById('ep_include_global_wb')?.addEventListener('change', (e) => { + ensureSettings().includeGlobalWorldbooks = e.target.value === 'true'; save(); + }); + + document.getElementById('ep_wb_pos4')?.addEventListener('change', (e) => { + ensureSettings().excludeWorldbookPosition4 = e.target.value === 'true'; save(); + }); + + document.getElementById('ep_wb_exclude_names')?.addEventListener('change', (e) => { + const raw = e.target.value ?? ''; + ensureSettings().worldbookExcludeNames = raw.split(',').map(t => t.trim()).filter(Boolean); + save(); + }); + + document.getElementById('ep_plot_n')?.addEventListener('change', (e) => { + ensureSettings().plotCount = Number(e.target.value) || 0; save(); + }); + + document.getElementById('ep_exclude_tags')?.addEventListener('change', (e) => { + const raw = e.target.value ?? ''; + ensureSettings().chatExcludeTags = raw.split(',').map(t => t.trim()).filter(Boolean); + save(); + }); + + // Logs — unified pointer handler for desktop + mobile + const logBtn = document.getElementById('ep_open_logs'); + if (logBtn) { + _addUniversalTap(logBtn, () => openLogModal()); + } + + document.getElementById('ep_test_planner')?.addEventListener('click', async () => { + try { + const fake = '(测试输入)我想让你帮我规划下一步剧情。'; + await runPlanningOnce(fake, true); + toastInfo('测试完成:去 Logs 查看。'); + } catch (e) { toastErr(String(e?.message ?? e)); } + }); + + // API + document.getElementById('ep_api_channel')?.addEventListener('change', (e) => { ensureSettings().api.channel = e.target.value; save(); }); + document.getElementById('ep_api_base')?.addEventListener('change', (e) => { ensureSettings().api.baseUrl = e.target.value.trim(); save(); }); + document.getElementById('ep_prefix_mode')?.addEventListener('change', (e) => { ensureSettings().api.prefixMode = e.target.value; save(); }); + document.getElementById('ep_prefix_custom')?.addEventListener('change', (e) => { ensureSettings().api.customPrefix = e.target.value.trim(); save(); }); + document.getElementById('ep_api_key')?.addEventListener('change', (e) => { ensureSettings().api.apiKey = e.target.value; save(); }); + document.getElementById('ep_model')?.addEventListener('change', (e) => { ensureSettings().api.model = e.target.value.trim(); save(); }); + document.getElementById('ep_stream')?.addEventListener('change', (e) => { ensureSettings().api.stream = e.target.value === 'true'; save(); }); + document.getElementById('ep_temp')?.addEventListener('change', (e) => { ensureSettings().api.temperature = Number(e.target.value); save(); }); + document.getElementById('ep_top_p')?.addEventListener('change', (e) => { ensureSettings().api.top_p = Number(e.target.value); save(); }); + document.getElementById('ep_top_k')?.addEventListener('change', (e) => { ensureSettings().api.top_k = Number(e.target.value) || 0; save(); }); + document.getElementById('ep_pp')?.addEventListener('change', (e) => { ensureSettings().api.presence_penalty = e.target.value.trim(); save(); }); + document.getElementById('ep_fp')?.addEventListener('change', (e) => { ensureSettings().api.frequency_penalty = e.target.value.trim(); save(); }); + document.getElementById('ep_mt')?.addEventListener('change', (e) => { ensureSettings().api.max_tokens = e.target.value.trim(); save(); }); + + document.getElementById('ep_test_conn')?.addEventListener('click', async () => { + try { + const models = await fetchModels(); + toastInfo(`连接成功:${models.length} 个模型`); + } catch (e) { toastErr(String(e?.message ?? e)); } + }); + + document.getElementById('ep_fetch_models')?.addEventListener('click', async () => { + try { + const models = await fetchModels(); + toastInfo(`拉取成功:${models.length} 个模型`); + state.logs.unshift({ + time: nowISO(), ok: true, model: 'GET /models', + requestMessages: [], rawReply: safeStringify(models), filteredReply: safeStringify(models) + }); + clampLogs(); persistLogsMaybe(); + openLogModal(); renderLogs(); + } catch (e) { toastErr(String(e?.message ?? e)); } + }); + + // Prompt designer + document.getElementById('ep_add_prompt')?.addEventListener('click', () => { + const s = ensureSettings(); + s.promptBlocks.push({ + id: crypto?.randomUUID?.() ?? String(Date.now()), + role: 'system', name: 'New Block', content: '' + }); + save(); renderPromptDesigner(); + }); + + document.getElementById('ep_reset_prompt')?.addEventListener('click', () => { + extension_settings[EXT_NAME].promptBlocks = getDefaultSettings().promptBlocks; + save(); renderPromptDesigner(); + }); + + // Template management + document.getElementById('ep_tpl_save')?.addEventListener('click', () => { + const sel = document.getElementById('ep_tpl_select'); + const name = sel?.value; + if (!name) { toastWarn('请先选择一个模板再储存'); return; } + const s = ensureSettings(); + if (!s.promptTemplates) s.promptTemplates = {}; + s.promptTemplates[name] = JSON.parse(JSON.stringify(s.promptBlocks || [])); + save(); + toastInfo(`模板「${name}」已覆盖保存`); + }); + document.getElementById('ep_tpl_select')?.addEventListener('change', (e) => { + const name = e.target.value; + if (!name) return; // 选的是占位符,不做事 + const s = ensureSettings(); + const tpl = s.promptTemplates?.[name]; + if (!tpl) { toastWarn('模板不存在'); return; } + s.promptBlocks = JSON.parse(JSON.stringify(tpl)).map(b => ({ + ...b, id: crypto?.randomUUID?.() ?? String(Date.now() + Math.random()) + })); + save(); renderPromptDesigner(); + toastInfo(`模板「${name}」已载入`); + }); + document.getElementById('ep_tpl_saveas')?.addEventListener('click', () => { + const name = prompt('请输入新模板名称:'); + if (!name || !name.trim()) return; + const s = ensureSettings(); + if (!s.promptTemplates) s.promptTemplates = {}; + s.promptTemplates[name.trim()] = JSON.parse(JSON.stringify(s.promptBlocks || [])); + save(); + refreshTemplateSelect(name.trim()); // 刷新并选中新模板 + toastInfo(`模板「${name.trim()}」已保存`); + }); + + document.getElementById('ep_tpl_delete')?.addEventListener('click', () => { + const sel = document.getElementById('ep_tpl_select'); + const name = sel?.value; + if (!name) { toastWarn('请先选择要删除的模板'); return; } + if (!confirm(`确定删除模板「${name}」?`)) return; + const s = ensureSettings(); + if (s.promptTemplates) delete s.promptTemplates[name]; + save(); + refreshTemplateSelect(); + toastInfo(`模板「${name}」已删除`); + }); + + function refreshTemplateSelect(selectName) { + const sel = document.getElementById('ep_tpl_select'); + if (!sel) return; + const s = ensureSettings(); + const names = Object.keys(s.promptTemplates || {}); + sel.innerHTML = '' + + names.map(n => ``).join(''); + } + + document.getElementById('ep_prompt_list')?.addEventListener('input', (e) => { + const s = ensureSettings(); + const id = e.target?.getAttribute?.('data-id'); + if (!id) return; + const b = s.promptBlocks.find(x => x.id === id); + if (!b) return; + if (e.target.classList.contains('ep_pb_name')) b.name = e.target.value; + if (e.target.classList.contains('ep_pb_content')) b.content = e.target.value; + save(); + }); + + document.getElementById('ep_prompt_list')?.addEventListener('change', (e) => { + const s = ensureSettings(); + const id = e.target?.getAttribute?.('data-id'); + if (!id) return; + const b = s.promptBlocks.find(x => x.id === id); + if (!b) return; + if (e.target.classList.contains('ep_pb_role')) b.role = e.target.value; + save(); + }); + + document.getElementById('ep_prompt_list')?.addEventListener('click', (e) => { + const s = ensureSettings(); + const id = e.target?.getAttribute?.('data-id'); + if (!id) return; + const idx = s.promptBlocks.findIndex(x => x.id === id); + if (idx < 0) return; + + if (e.target.classList.contains('ep_pb_del')) { + s.promptBlocks.splice(idx, 1); save(); renderPromptDesigner(); + } + if (e.target.classList.contains('ep_pb_up') && idx > 0) { + [s.promptBlocks[idx - 1], s.promptBlocks[idx]] = [s.promptBlocks[idx], s.promptBlocks[idx - 1]]; + save(); renderPromptDesigner(); + } + if (e.target.classList.contains('ep_pb_down') && idx < s.promptBlocks.length - 1) { + [s.promptBlocks[idx + 1], s.promptBlocks[idx]] = [s.promptBlocks[idx], s.promptBlocks[idx + 1]]; + save(); renderPromptDesigner(); + } + }); + + // Debug buttons + document.getElementById('ep_debug_worldbook')?.addEventListener('click', async () => { + const out = document.getElementById('ep_debug_output'); + if (!out) return; + out.style.display = 'block'; + out.textContent = '正在诊断世界书读取...\n'; + try { + const charWb = await getCharacterWorldbooks(); + out.textContent += `角色世界书名称: ${JSON.stringify(charWb)}\n`; + const globalWb = await getGlobalWorldbooks(); + out.textContent += `全局世界书名称: ${JSON.stringify(globalWb)}\n`; + const all = [...new Set([...charWb, ...globalWb])]; + for (const name of all) { + const data = await getWorldbookData(name); + const count = data?.entries?.length ?? 0; + const enabled = data?.entries?.filter(e => !e?.disable && !e?.disabled)?.length ?? 0; + out.textContent += ` "${name}": ${count} 条目, ${enabled} 已启用\n`; + } + if (!all.length) { + out.textContent += '⚠️ 未找到任何世界书。请检查角色卡是否绑定了世界书。\n'; + // Extra diagnostics + const charObj = getCurrentCharSafe(); + out.textContent += `charObj存在: ${!!charObj}\n`; + if (charObj) { + out.textContent += `charObj.world: ${charObj?.world}\n`; + out.textContent += `charObj.data.extensions.world: ${charObj?.data?.extensions?.world}\n`; + } + const ctx = getContextSafe(); + out.textContent += `ctx存在: ${!!ctx}\n`; + if (ctx) { + out.textContent += `ctx.characterId: ${ctx?.characterId}\n`; + out.textContent += `ctx.this_chid: ${ctx?.this_chid}\n`; + } + } + } catch (e) { out.textContent += `错误: ${e?.message ?? e}\n`; } + }); + + document.getElementById('ep_debug_char')?.addEventListener('click', () => { + const out = document.getElementById('ep_debug_output'); + if (!out) return; + out.style.display = 'block'; + const charObj = getCurrentCharSafe(); + if (!charObj) { + out.textContent = '⚠️ 未检测到角色。\n'; + const ctx = getContextSafe(); + out.textContent += `ctx: ${!!ctx}, ctx.characterId: ${ctx?.characterId}, ctx.this_chid: ${ctx?.this_chid}\n`; + out.textContent += `window.this_chid: ${window.this_chid}\n`; + out.textContent += `window.characters count: ${window.characters?.length ?? 'N/A'}\n`; + return; + } + const block = formatCharCardBlock(charObj); + out.textContent = `角色名: ${charObj?.name}\n`; + out.textContent += `desc长度: ${(charObj?.description ?? '').length}\n`; + out.textContent += `personality长度: ${(charObj?.personality ?? '').length}\n`; + out.textContent += `scenario长度: ${(charObj?.scenario ?? '').length}\n`; + out.textContent += `world: ${charObj?.world ?? charObj?.data?.extensions?.world ?? '(无)'}\n`; + out.textContent += `---\n${block.slice(0, 500)}...\n`; + }); +} + +function injectUI() { + ensureSettings(); + loadPersistedLogsMaybe(); + + if (document.getElementById('ena_planner_settings')) return; + + const container = document.getElementById('ena_planner_panel'); + if (!container) return; + + const wrap = document.createElement('div'); + wrap.innerHTML = createSettingsHTML(); + container.appendChild(wrap.firstElementChild); + + // Log modal + if (!document.getElementById('ep_log_modal')) { + const modalWrap = document.createElement('div'); + modalWrap.innerHTML = createLogModalHTML(); + // Append all children (style + modal div) + while (modalWrap.firstChild) document.body.appendChild(modalWrap.firstChild); + + _addUniversalTap(document.getElementById('ep_log_close'), () => closeLogModal()); + // Backdrop tap to close + const logModal = document.getElementById('ep_log_modal'); + if (logModal) { + _addUniversalTap(logModal, (e) => { if (e.target === logModal) closeLogModal(); }); + } + document.getElementById('ep_log_clear')?.addEventListener('click', () => { + state.logs = []; persistLogsMaybe(); renderLogs(); + }); + document.getElementById('ep_log_export')?.addEventListener('click', () => { + try { + const blob = new Blob([JSON.stringify(state.logs, null, 2)], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; a.download = `ena-planner-logs-${Date.now()}.json`; a.click(); + URL.revokeObjectURL(url); + } catch (e) { toastErr('导出失败:' + String(e?.message ?? e)); } + }); + } + + bindSettingsUI(); +} + +/** ------------------------- + * Planning runner + logging + * --------------------------*/ +async function runPlanningOnce(rawUserInput, silent = false) { + const s = ensureSettings(); + + const log = { + time: nowISO(), ok: false, model: s.api.model, + requestMessages: [], rawReply: '', filteredReply: '', error: '' + }; + + try { + const { messages } = await buildPlannerMessages(rawUserInput); + log.requestMessages = messages; + + const rawReply = await callPlanner(messages); + log.rawReply = rawReply; + + const filtered = filterPlannerForInput(rawReply); + log.filteredReply = filtered; + log.ok = true; + + state.logs.unshift(log); clampLogs(); persistLogsMaybe(); + return { rawReply, filtered }; + } catch (e) { + log.error = String(e?.message ?? e); + state.logs.unshift(log); clampLogs(); persistLogsMaybe(); + if (!silent) toastErr(log.error); + throw e; + } +} + +/** ------------------------- + * Intercept send + * --------------------------*/ +function getSendTextarea() { return document.getElementById('send_textarea'); } +function getSendButton() { return document.getElementById('send_but') || document.getElementById('send_button'); } + +function shouldInterceptNow() { + const s = ensureSettings(); + if (!s.enabled || state.isPlanning) return false; + const ta = getSendTextarea(); + if (!ta) return false; + const txt = String(ta.value ?? '').trim(); + if (!txt) return false; + if (state.bypassNextSend) return false; + if (s.skipIfPlotPresent && / { state.bypassNextSend = false; }, 800); + } +} + +function installSendInterceptors() { + document.addEventListener('click', (e) => { + const btn = getSendButton(); + if (!btn) return; + if (e.target !== btn && !btn.contains(e.target)) return; + if (!shouldInterceptNow()) return; + e.preventDefault(); + e.stopImmediatePropagation(); + doInterceptAndPlanThenSend().catch(err => toastErr(String(err?.message ?? err))); + }, true); + + document.addEventListener('keydown', (e) => { + const ta = getSendTextarea(); + if (!ta || e.target !== ta) return; + if (e.key === 'Enter' && !e.shiftKey) { + if (!shouldInterceptNow()) return; + e.preventDefault(); + e.stopImmediatePropagation(); + doInterceptAndPlanThenSend().catch(err => toastErr(String(err?.message ?? err))); + } + }, true); +} + +export function initEnaPlanner() { + ensureSettings(); + loadPersistedLogsMaybe(); + + // Wait for DOM to be ready with the panel + const tryInject = () => { + if (document.getElementById('ena_planner_panel')) { + injectUI(); + installSendInterceptors(); + } else { + setTimeout(tryInject, 500); + } + }; + tryInject(); +} \ No newline at end of file From de9c126cd3093a26ad9c865d8dac7ee7d96b5acc Mon Sep 17 00:00:00 2001 From: Hao19911125 <99091644+Hao19911125@users.noreply.github.com> Date: Wed, 18 Feb 2026 22:33:31 +0800 Subject: [PATCH 07/26] Update manifest.json --- manifest.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manifest.json b/manifest.json index d6fb4f5..6e28037 100644 --- a/manifest.json +++ b/manifest.json @@ -1,5 +1,5 @@ { - "display_name": "LittleWhiteBox", + "display_name": "HaoLittleWhiteBox", "loading_order": 10, "requires": [], "optional": [], @@ -9,4 +9,4 @@ "version": "2.5.0", "homePage": "https://github.com/RT15548/LittleWhiteBox", "generate_interceptor": "xiaobaixGenerateInterceptor" -} \ No newline at end of file +} From 560f890e8d8a3244b44e9ae79eb14698a7fc166d Mon Sep 17 00:00:00 2001 From: Hao19911125 <99091644+Hao19911125@users.noreply.github.com> Date: Wed, 18 Feb 2026 22:47:36 +0800 Subject: [PATCH 08/26] Rename ena-planner.js.js to ena-planner.js --- modules/ena-planner/{ena-planner.js.js => ena-planner.js} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename modules/ena-planner/{ena-planner.js.js => ena-planner.js} (99%) diff --git a/modules/ena-planner/ena-planner.js.js b/modules/ena-planner/ena-planner.js similarity index 99% rename from modules/ena-planner/ena-planner.js.js rename to modules/ena-planner/ena-planner.js index 42ff459..620e04f 100644 --- a/modules/ena-planner/ena-planner.js.js +++ b/modules/ena-planner/ena-planner.js @@ -1976,4 +1976,4 @@ export function initEnaPlanner() { } }; tryInject(); -} \ No newline at end of file +} From 0112fd3f19400381dbae3e7f149ea72587b45d88 Mon Sep 17 00:00:00 2001 From: Hao19911125 <99091644+Hao19911125@users.noreply.github.com> Date: Wed, 18 Feb 2026 22:47:52 +0800 Subject: [PATCH 09/26] Rename ena-planner.css.css to ena-planner.css --- modules/ena-planner/{ena-planner.css.css => ena-planner.css} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename modules/ena-planner/{ena-planner.css.css => ena-planner.css} (100%) diff --git a/modules/ena-planner/ena-planner.css.css b/modules/ena-planner/ena-planner.css similarity index 100% rename from modules/ena-planner/ena-planner.css.css rename to modules/ena-planner/ena-planner.css From 615c57dad0d9051d48ec2745ac5bb38b94037f62 Mon Sep 17 00:00:00 2001 From: Hao19911125 <99091644+Hao19911125@users.noreply.github.com> Date: Wed, 18 Feb 2026 22:53:41 +0800 Subject: [PATCH 10/26] Update ena-planner.js --- modules/ena-planner/ena-planner.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ena-planner/ena-planner.js b/modules/ena-planner/ena-planner.js index 620e04f..a998a3d 100644 --- a/modules/ena-planner/ena-planner.js +++ b/modules/ena-planner/ena-planner.js @@ -1,5 +1,5 @@ -import { extension_settings } from '../../../../extensions.js'; -import { getRequestHeaders, saveSettingsDebounced, substituteParamsExtended } from '../../../../../script.js'; +import { extension_settings } from '../../../../../extensions.js'; +import { getRequestHeaders, saveSettingsDebounced, substituteParamsExtended } from '../../../../../../script.js'; import { getStorySummaryForEna } from '../story-summary/story-summary.js'; const EXT_NAME = 'ena-planner'; From adcfc38ded00ac56ec319f94cdae149fa984080f Mon Sep 17 00:00:00 2001 From: Hao19911125 <99091644+Hao19911125@users.noreply.github.com> Date: Sat, 21 Feb 2026 17:07:42 +0800 Subject: [PATCH 11/26] Create sync-upstream.yml --- .github/workflows/sync-upstream.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/sync-upstream.yml diff --git a/.github/workflows/sync-upstream.yml b/.github/workflows/sync-upstream.yml new file mode 100644 index 0000000..3f7a721 --- /dev/null +++ b/.github/workflows/sync-upstream.yml @@ -0,0 +1,24 @@ +name: Sync Upstream + +on: + schedule: + - cron: '0 * * * *' # 每小时检查一次 + workflow_dispatch: # 也可以在 GitHub 手动触发 + +jobs: + sync: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + token: ${{ secrets.GITHUB_TOKEN }} + fetch-depth: 0 + + - name: Sync from upstream + uses: aormsby/Fork-Sync-With-Upstream-action@v3.4 + with: + upstream_sync_repo: RT15548/LittleWhiteBox + upstream_sync_branch: main + target_sync_branch: main + target_repo_token: ${{ secrets.GITHUB_TOKEN }} + test_mode: false From f69dade415bcfa8d767f6f093effbee961fa9772 Mon Sep 17 00:00:00 2001 From: Hao19911125 <99091644+Hao19911125@users.noreply.github.com> Date: Sat, 21 Feb 2026 21:22:47 +0800 Subject: [PATCH 12/26] Update ena-planner.js --- modules/ena-planner/ena-planner.js | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/modules/ena-planner/ena-planner.js b/modules/ena-planner/ena-planner.js index a998a3d..218284d 100644 --- a/modules/ena-planner/ena-planner.js +++ b/modules/ena-planner/ena-planner.js @@ -1825,6 +1825,28 @@ function injectUI() { if (document.getElementById('ena_planner_settings')) return; + // 动态注入 tab 按钮 + const menuBar = document.querySelector('.settings-menu-vertical'); + if (!menuBar) return; + if (!menuBar.querySelector('[data-target="ena-planner"]')) { + const tabDiv = document.createElement('div'); + tabDiv.className = 'menu-tab'; + tabDiv.setAttribute('data-target', 'ena-planner'); + tabDiv.setAttribute('style', 'border-bottom:1px solid #303030;'); + tabDiv.innerHTML = '剧情规划'; + menuBar.appendChild(tabDiv); + } + + // 动态注入面板容器 + const contentArea = document.querySelector('.settings-content'); + if (!contentArea) return; + if (!document.getElementById('ena_planner_panel')) { + const panel = document.createElement('div'); + panel.id = 'ena_planner_panel'; + panel.className = 'ena-planner settings-section'; + contentArea.appendChild(panel); + } + const container = document.getElementById('ena_planner_panel'); if (!container) return; @@ -1836,11 +1858,9 @@ function injectUI() { if (!document.getElementById('ep_log_modal')) { const modalWrap = document.createElement('div'); modalWrap.innerHTML = createLogModalHTML(); - // Append all children (style + modal div) while (modalWrap.firstChild) document.body.appendChild(modalWrap.firstChild); _addUniversalTap(document.getElementById('ep_log_close'), () => closeLogModal()); - // Backdrop tap to close const logModal = document.getElementById('ep_log_modal'); if (logModal) { _addUniversalTap(logModal, (e) => { if (e.target === logModal) closeLogModal(); }); @@ -1966,9 +1986,8 @@ export function initEnaPlanner() { ensureSettings(); loadPersistedLogsMaybe(); - // Wait for DOM to be ready with the panel const tryInject = () => { - if (document.getElementById('ena_planner_panel')) { + if (document.querySelector('.settings-menu-vertical')) { injectUI(); installSendInterceptors(); } else { From 4b70468cfb08464fdaf41364d2879fd9960db373 Mon Sep 17 00:00:00 2001 From: Hao19911125 <99091644+Hao19911125@users.noreply.github.com> Date: Sat, 21 Feb 2026 21:32:55 +0800 Subject: [PATCH 13/26] Update ena-planner.js --- modules/ena-planner/ena-planner.js | 475 +++++++++++++++-------------- 1 file changed, 248 insertions(+), 227 deletions(-) diff --git a/modules/ena-planner/ena-planner.js b/modules/ena-planner/ena-planner.js index 218284d..77c23dc 100644 --- a/modules/ena-planner/ena-planner.js +++ b/modules/ena-planner/ena-planner.js @@ -1270,238 +1270,259 @@ function createSettingsHTML() { const channel = s.api.channel; return ` -
-
-
- Ena Planner - - - ${s.enabled ? 'Enabled' : 'Disabled'} - -
+ +
+
+ Ena Planner + + + ${s.enabled ? 'Enabled' : 'Disabled'} + +
+ +
+
总览
+
API
+
提示词
+
调试
+
+ + +
+
+
+ + +
开启后:你点发送/回车,会先走"规划模型",把规划结果写回输入框再发送。
+
+
+ + +
防止"原始+规划文本"再次被拦截规划。
+
-
-
-
总览
-
API
-
提示词
-
调试
+
+ +
+
+ + +
角色绑定的世界书总是会读取。这里选择是否额外包含全局世界书。
- - -
-
-
- - -
开启后:你点发送/回车,会先走"规划模型",把规划结果写回输入框再发送。
-
- -
- - -
防止"原始+规划文本"再次被拦截规划。
-
-
- -
- -
-
- - -
角色绑定的世界书总是会读取。这里选择是否额外包含全局世界书。
-
- -
- - -
-
- -
-
- - -
条目名称/备注包含这些字符串的条目会被排除。
-
-
- -
-
- - -
-
- -
- 自动行为说明:
- · 聊天片段:自动读取所有未隐藏的 AI 回复(不含用户输入)
- · 自动剔除 <think> 以前的内容(含未包裹的思考段落)
- · 角色卡字段(desc/personality/scenario):有就全部加入
- · 向量召回(extensionPrompts):有就自动加入
- · 世界书激活:常驻(蓝灯)+ 关键词触发(绿灯) -
- -
- -
-
- - -
这些 XML 标签及其内容会从聊天历史中剔除。自闭合标签(如 <Tag/>)也会被移除。
-
-
- -
- - -
+
+ +
- - -
-
-
- - -
影响默认前缀:OpenAI/Claude → /v1,Gemini → /v1beta
-
- -
- - -
-
- -
-
- - -
-
- - -
-
- -
-
- - -
-
- -
-
- - -
-
- - -
-
- -
- - -
- -
- -
-
- - -
-
- - -
-
- - -
-
- -
-
- - -
-
- - -
-
- - -
-
-
- - -
-
新增多条提示词块,选择 role(system/user/assistant)。系统块放最前面;assistant 块放最后。
- -
- - - - -
- -
-
- - -
-
- - -
-
- 工作原理:
- · 规划时会锁定发送按钮
- · Log 静默记录,只有出错才弹提示
- · 写回版本:剔除 <think>,只保留 <plot>+<note>
- · 前文自动剔除 <think> 以前内容和排除标签内容 -
-
- - -
- -
-
+ +
+
+ + +
条目名称/备注包含这些字符串的条目会被排除。
+
+
+ +
+
+ + +
+
+ +
+ 自动行为说明:
+ · 聊天片段:自动读取所有未隐藏的 AI 回复(不含用户输入)
+ · 自动剔除 <think> 以前的内容(含未包裹的思考段落)
+ · 角色卡字段(desc/personality/scenario):有就全部加入
+ · 向量召回(extensionPrompts):有就自动加入
+ · 世界书激活:常驻(蓝灯)+ 关键词触发(绿灯) +
+ +
+ +
+
+ + +
这些 XML 标签及其内容会从聊天历史中剔除。自闭合标签(如 <Tag/>)也会被移除。
+
+
+ +
+ + +
+
+ + +
+
+
+ + +
影响默认前缀:OpenAI/Claude → /v1,Gemini → /v1beta
+
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
新增多条提示词块,选择 role(system/user/assistant)。系统块放最前面;assistant 块放最后。
+ +
+ + + + +
+ +
+
+ + +
+
+ + +
+
+ 工作原理:
+ · 规划时会锁定发送按钮
+ · Log 静默记录,只有出错才弹提示
+ · 写回版本:剔除 <think>,只保留 <plot>+<note>
+ · 前文自动剔除 <think> 以前内容和排除标签内容 +
+
+ + +
+
`; } From 85ee19126148a10b0cbdb15f2bd5244595a53321 Mon Sep 17 00:00:00 2001 From: Hao19911125 <99091644+Hao19911125@users.noreply.github.com> Date: Sat, 21 Feb 2026 21:35:24 +0800 Subject: [PATCH 14/26] Update ena-planner.css --- modules/ena-planner/ena-planner.css | 38 ++++++++++++++--------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/modules/ena-planner/ena-planner.css b/modules/ena-planner/ena-planner.css index b532ba4..1e3b9c5 100644 --- a/modules/ena-planner/ena-planner.css +++ b/modules/ena-planner/ena-planner.css @@ -1,52 +1,52 @@ /* Ena Planner v0.5 — collapsible, clean */ /* ===== Settings panel inside inline-drawer ===== */ -#ena_planner_settings { +#ena_planner_panel { padding: 8px 0; } -#ena_planner_settings .ep-row { +#ena_planner_panel .ep-row { display: flex; gap: 10px; flex-wrap: wrap; margin: 8px 0; } -#ena_planner_settings label { +#ena_planner_panel label { font-size: 12px; opacity: .9; display: block; margin-bottom: 4px; } -#ena_planner_settings input[type="text"], -#ena_planner_settings input[type="password"], -#ena_planner_settings input[type="number"], -#ena_planner_settings select, -#ena_planner_settings textarea { +#ena_planner_panel input[type="text"], +#ena_planner_panel input[type="password"], +#ena_planner_panel input[type="number"], +#ena_planner_panel select, +#ena_planner_panel textarea { width: 100%; box-sizing: border-box; } -#ena_planner_settings .ep-col { +#ena_planner_panel .ep-col { flex: 1 1 220px; min-width: 220px; } -#ena_planner_settings .ep-col.wide { +#ena_planner_panel .ep-col.wide { flex: 1 1 100%; min-width: 260px; } /* Tabs */ -#ena_planner_settings .ep-tabs { +#ena_planner_panel .ep-tabs { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; } -#ena_planner_settings .ep-tab { +#ena_planner_panel .ep-tab { padding: 6px 10px; border-radius: 999px; cursor: pointer; @@ -56,33 +56,33 @@ font-size: 13px; } -#ena_planner_settings .ep-tab.active { +#ena_planner_panel .ep-tab.active { opacity: 1; background: rgba(255,255,255,.06); } -#ena_planner_settings .ep-panel { +#ena_planner_panel .ep-panel { display: none; } -#ena_planner_settings .ep-panel.active { +#ena_planner_panel .ep-panel.active { display: block; } -#ena_planner_settings .ep-actions { +#ena_planner_panel .ep-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 10px; } -#ena_planner_settings .ep-hint { +#ena_planner_panel .ep-hint { font-size: 11px; opacity: .7; margin-top: 4px; } -#ena_planner_settings .ep-hint-box { +#ena_planner_panel .ep-hint-box { font-size: 12px; opacity: .85; margin: 10px 0; @@ -93,7 +93,7 @@ line-height: 1.6; } -#ena_planner_settings .ep-divider { +#ena_planner_panel .ep-divider { margin: 10px 0; border-top: 1px dashed rgba(255,255,255,.15); } From e7de320d2d97ee9a2fad4d3d570ac8906a174063 Mon Sep 17 00:00:00 2001 From: Hao19911125 <99091644+Hao19911125@users.noreply.github.com> Date: Sat, 21 Feb 2026 21:36:01 +0800 Subject: [PATCH 15/26] Update ena-planner.js --- modules/ena-planner/ena-planner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ena-planner/ena-planner.js b/modules/ena-planner/ena-planner.js index 77c23dc..8301fea 100644 --- a/modules/ena-planner/ena-planner.js +++ b/modules/ena-planner/ena-planner.js @@ -1560,7 +1560,7 @@ function renderPromptDesigner() { } function bindSettingsUI() { - const settingsEl = document.getElementById('ena_planner_settings'); + const settingsEl = document.getElementById('ena_planner_panel'); if (!settingsEl) return; // Tabs From 0ceaa847598a27706e2e4472963b9c5f23de4054 Mon Sep 17 00:00:00 2001 From: Hao19911125 <99091644+Hao19911125@users.noreply.github.com> Date: Sat, 21 Feb 2026 21:40:27 +0800 Subject: [PATCH 16/26] Update ena-planner.js --- modules/ena-planner/ena-planner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ena-planner/ena-planner.js b/modules/ena-planner/ena-planner.js index 8301fea..4ec8a5e 100644 --- a/modules/ena-planner/ena-planner.js +++ b/modules/ena-planner/ena-planner.js @@ -1873,7 +1873,7 @@ function injectUI() { const wrap = document.createElement('div'); wrap.innerHTML = createSettingsHTML(); - container.appendChild(wrap.firstElementChild); + while (wrap.firstChild) container.appendChild(wrap.firstChild); // Log modal if (!document.getElementById('ep_log_modal')) { From 6057fac25e7ac55826ad747c6bafa8086e975a87 Mon Sep 17 00:00:00 2001 From: Hao19911125 <99091644+Hao19911125@users.noreply.github.com> Date: Sat, 21 Feb 2026 21:47:21 +0800 Subject: [PATCH 17/26] Update ena-planner.js --- modules/ena-planner/ena-planner.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/ena-planner/ena-planner.js b/modules/ena-planner/ena-planner.js index 4ec8a5e..046bd23 100644 --- a/modules/ena-planner/ena-planner.js +++ b/modules/ena-planner/ena-planner.js @@ -1293,7 +1293,7 @@ function createSettingsHTML() { #ena_planner_panel input[type="password"], #ena_planner_panel input[type="number"] { width:100%; padding:6px 8px; border-radius:4px; border:1px solid var(--SmartThemeBorderColor,#444); - background:var(--SmartThemeBotMesBlurTintColor,#1a1a2e); color:var(--SmartThemeBodyColor,#ccc); font-size:13px; + background:#1a1a2e; color:#e0e0e0; font-size:13px; } #ena_planner_panel .menu_button { display:inline-block; white-space:nowrap; } .ep-prompt-block { border:1px solid var(--SmartThemeBorderColor,#444); border-radius:6px; padding:8px; margin-bottom:8px; } @@ -1865,6 +1865,7 @@ function injectUI() { const panel = document.createElement('div'); panel.id = 'ena_planner_panel'; panel.className = 'ena-planner settings-section'; + panel.style.display = 'none'; contentArea.appendChild(panel); } From 1f387c6a3ed8aa74ac6705e75239262f57721341 Mon Sep 17 00:00:00 2001 From: Hao19911125 <99091644+Hao19911125@users.noreply.github.com> Date: Sat, 21 Feb 2026 22:03:52 +0800 Subject: [PATCH 18/26] Update ena-planner.js --- modules/ena-planner/ena-planner.js | 32 +++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/modules/ena-planner/ena-planner.js b/modules/ena-planner/ena-planner.js index 046bd23..d4e1584 100644 --- a/modules/ena-planner/ena-planner.js +++ b/modules/ena-planner/ena-planner.js @@ -718,16 +718,21 @@ function buildEjsContext() { // getvar: read a chat variable (supports dot-path for nested objects) function getvar(name) { if (!name) return ''; - // Direct lookup first - if (vars[name] !== undefined) return vars[name]; - // Try dot-path traversal - const parts = String(name).split('.'); - let cur = vars; - for (const p of parts) { - if (cur == null || typeof cur !== 'object') return ''; - cur = cur[p]; + let val; + if (vars[name] !== undefined) { + val = vars[name]; + } else { + const parts = String(name).split('.'); + let cur = vars; + for (const p of parts) { + if (cur == null || typeof cur !== 'object') return ''; + cur = cur[p]; + } + val = cur ?? ''; } - return cur ?? ''; + // 处理字符串布尔值 + if (val === 'false' || val === 'False' || val === '0') return ''; + return val; } // setvar: write a chat variable (no-op for our purposes, just to avoid errors) @@ -1299,6 +1304,15 @@ function createSettingsHTML() { .ep-prompt-block { border:1px solid var(--SmartThemeBorderColor,#444); border-radius:6px; padding:8px; margin-bottom:8px; } .ep-prompt-head { display:flex; justify-content:space-between; align-items:center; margin-bottom:6px; flex-wrap:wrap; gap:6px; } .ep-prompt-block textarea { width:100%; background:var(--SmartThemeBotMesBlurTintColor,#1a1a2e); color:var(--SmartThemeBodyColor,#ccc); border:1px solid var(--SmartThemeBorderColor,#444); border-radius:4px; padding:6px; font-size:12px; } + #ena_planner_panel .ep-prompt-block textarea { + background:#1a1a2e; color:#e0e0e0; border:1px solid var(--SmartThemeBorderColor,#444); + } + .ep-log-pre { + background:rgba(20,20,30,0.95) !important; color:#e0e0e0 !important; + } + .ep-log-modal .ep-log-card { + background:rgba(20,20,30,0.97); color:#e0e0e0; + }
From cfcc24142e338584b99034b05d936855317acef1 Mon Sep 17 00:00:00 2001 From: Hao19911125 <99091644+Hao19911125@users.noreply.github.com> Date: Sat, 21 Feb 2026 22:32:04 +0800 Subject: [PATCH 19/26] Update ena-planner.js --- modules/ena-planner/ena-planner.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/ena-planner/ena-planner.js b/modules/ena-planner/ena-planner.js index d4e1584..4437c63 100644 --- a/modules/ena-planner/ena-planner.js +++ b/modules/ena-planner/ena-planner.js @@ -730,8 +730,9 @@ function buildEjsContext() { } val = cur ?? ''; } - // 处理字符串布尔值 - if (val === 'false' || val === 'False' || val === '0') return ''; + // 字符串布尔值转为真正的布尔值 + if (val === 'false' || val === 'False' || val === 'FALSE') return false; + if (val === 'true' || val === 'True' || val === 'TRUE') return true; return val; } From d97dc0b2bf87b1a86f4dd432c00969972fc1a3e0 Mon Sep 17 00:00:00 2001 From: Hao19911125 <99091644+Hao19911125@users.noreply.github.com> Date: Sat, 21 Feb 2026 22:53:38 +0800 Subject: [PATCH 20/26] Update ena-planner.js --- modules/ena-planner/ena-planner.js | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/modules/ena-planner/ena-planner.js b/modules/ena-planner/ena-planner.js index 4437c63..c925b3a 100644 --- a/modules/ena-planner/ena-planner.js +++ b/modules/ena-planner/ena-planner.js @@ -334,7 +334,26 @@ function collectRecentChatSnippet(chat, maxMessages) { } function getCachedStorySummary() { - return getStorySummaryForEna(); + const live = getStorySummaryForEna(); + const ctx = getContextSafe(); + const meta = ctx?.chatMetadata ?? window.chat_metadata; + + if (live && live.trim().length > 30) { + // 拿到了新的,存起来 + if (meta) { + meta.ena_cached_story_summary = live; + saveSettingsDebounced(); + } + return live; + } + + // 没拿到(首轮/重启),从 chat_metadata 读上次的 + if (meta?.ena_cached_story_summary) { + console.log('[EnaPlanner] Using persisted story summary from chat_metadata'); + return meta.ena_cached_story_summary; + } + + return ''; } /** ------------------------- From 514309b66916df663b67be22f0ab1c72b994de5c Mon Sep 17 00:00:00 2001 From: Hao19911125 <99091644+Hao19911125@users.noreply.github.com> Date: Tue, 24 Feb 2026 15:54:56 +0800 Subject: [PATCH 21/26] Update manifest.json --- manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index 6e28037..02136d3 100644 --- a/manifest.json +++ b/manifest.json @@ -1,5 +1,5 @@ { - "display_name": "HaoLittleWhiteBox", + "display_name": "LittleWhiteBox", "loading_order": 10, "requires": [], "optional": [], From 333a3c839d00bfbbab5760d4b866e90f2775f891 Mon Sep 17 00:00:00 2001 From: RT15548 <168917470+RT15548@users.noreply.github.com> Date: Tue, 24 Feb 2026 18:05:28 +0800 Subject: [PATCH 22/26] Delete .github/workflows/sync-upstream.yml --- .github/workflows/sync-upstream.yml | 24 ------------------------ 1 file changed, 24 deletions(-) delete mode 100644 .github/workflows/sync-upstream.yml diff --git a/.github/workflows/sync-upstream.yml b/.github/workflows/sync-upstream.yml deleted file mode 100644 index 3f7a721..0000000 --- a/.github/workflows/sync-upstream.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Sync Upstream - -on: - schedule: - - cron: '0 * * * *' # 每小时检查一次 - workflow_dispatch: # 也可以在 GitHub 手动触发 - -jobs: - sync: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - token: ${{ secrets.GITHUB_TOKEN }} - fetch-depth: 0 - - - name: Sync from upstream - uses: aormsby/Fork-Sync-With-Upstream-action@v3.4 - with: - upstream_sync_repo: RT15548/LittleWhiteBox - upstream_sync_branch: main - target_sync_branch: main - target_repo_token: ${{ secrets.GITHUB_TOKEN }} - test_mode: false From 86e08348af6743ce030b76c5c59ab59769dc7ea8 Mon Sep 17 00:00:00 2001 From: Hao19911125 <99091644+Hao19911125@users.noreply.github.com> Date: Wed, 25 Feb 2026 09:33:24 +0800 Subject: [PATCH 23/26] Update ena-planner.js for vector logic --- modules/ena-planner/ena-planner.js | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/modules/ena-planner/ena-planner.js b/modules/ena-planner/ena-planner.js index c925b3a..d578ae9 100644 --- a/modules/ena-planner/ena-planner.js +++ b/modules/ena-planner/ena-planner.js @@ -1,6 +1,8 @@ import { extension_settings } from '../../../../../extensions.js'; import { getRequestHeaders, saveSettingsDebounced, substituteParamsExtended } from '../../../../../../script.js'; import { getStorySummaryForEna } from '../story-summary/story-summary.js'; +import { buildVectorPromptText } from '../story-summary/generate/prompt.js'; +import { getVectorConfig } from '../story-summary/data/config.js'; const EXT_NAME = 'ena-planner'; @@ -1082,8 +1084,23 @@ async function buildPlannerMessages(rawUserInput) { const charBlockRaw = formatCharCardBlock(charObj); - // --- Story summary (cached from previous generation via interceptor) --- - const cachedSummary = getCachedStorySummary(); + // --- Story memory: try fresh recall with current user input --- + let cachedSummary = ''; + try { + const vectorCfg = getVectorConfig(); + if (vectorCfg?.enabled) { + const r = await buildVectorPromptText(false, { + pendingUserMessage: rawUserInput, + }); + cachedSummary = r?.text?.trim() || ''; + } + } catch (e) { + console.warn('[Ena] Fresh vector recall failed, falling back to cached data:', e); + } + // Fallback: use stale cache from previous generation + if (!cachedSummary) { + cachedSummary = getCachedStorySummary(); + } // --- Chat history: last 2 AI messages (floors N-1 & N-3) --- // Two messages instead of one to avoid cross-device cache miss: @@ -1093,7 +1110,7 @@ async function buildPlannerMessages(rawUserInput) { const recentChatRaw = collectRecentChatSnippet(chat, 2); const plotsRaw = formatPlotsBlock(extractLastNPlots(chat, s.plotCount)); - const vectorRaw = formatVectorRecallBlock(extPrompts); + const vectorRaw = ''; // Now included in fresh cachedSummary above // Build scanText for worldbook keyword activation const scanText = [charBlockRaw, cachedSummary, recentChatRaw, vectorRaw, plotsRaw, rawUserInput].join('\n\n'); @@ -1123,7 +1140,7 @@ async function buildPlannerMessages(rawUserInput) { // 3) Worldbook if (String(worldbook).trim()) messages.push({ role: 'system', content: worldbook }); - // 3.5) Cached story summary (小白X 剧情记忆 from previous turn) + // 3.5) Story memory (小白X <剧情记忆> — fresh recall when available, stale cache as fallback) if (storySummary.trim()) { messages.push({ role: 'system', content: `\n${storySummary}\n` }); } @@ -1131,7 +1148,8 @@ async function buildPlannerMessages(rawUserInput) { // 4) Chat history (last 2 AI responses — floors N-1 & N-3) if (String(recentChat).trim()) messages.push({ role: 'system', content: recentChat }); - // 5) Vector recall + // 5) Vector recall — now merged into story_summary above, kept for compatibility + // (vectorRaw is empty; this block intentionally does nothing) if (String(vector).trim()) messages.push({ role: 'system', content: vector }); // 6) Previous plots From c8fc81c3b7595693f06fe9d895b261d2586cb0bb Mon Sep 17 00:00:00 2001 From: Hao19911125 <99091644+Hao19911125@users.noreply.github.com> Date: Wed, 25 Feb 2026 10:28:45 +0800 Subject: [PATCH 24/26] Update ena-planner.js for vector log and sequence --- modules/ena-planner/ena-planner.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/modules/ena-planner/ena-planner.js b/modules/ena-planner/ena-planner.js index d578ae9..6daa72c 100644 --- a/modules/ena-planner/ena-planner.js +++ b/modules/ena-planner/ena-planner.js @@ -1086,6 +1086,7 @@ async function buildPlannerMessages(rawUserInput) { // --- Story memory: try fresh recall with current user input --- let cachedSummary = ''; + let _recallSource = 'none'; // ← 新增 try { const vectorCfg = getVectorConfig(); if (vectorCfg?.enabled) { @@ -1093,6 +1094,7 @@ async function buildPlannerMessages(rawUserInput) { pendingUserMessage: rawUserInput, }); cachedSummary = r?.text?.trim() || ''; + if (cachedSummary) _recallSource = 'fresh'; // ← 新增 } } catch (e) { console.warn('[Ena] Fresh vector recall failed, falling back to cached data:', e); @@ -1100,7 +1102,9 @@ async function buildPlannerMessages(rawUserInput) { // Fallback: use stale cache from previous generation if (!cachedSummary) { cachedSummary = getCachedStorySummary(); + if (cachedSummary) _recallSource = 'stale'; // ← 新增 } + console.log(`[Ena] Story memory source: ${_recallSource}`); // ← 新增 // --- Chat history: last 2 AI messages (floors N-1 & N-3) --- // Two messages instead of one to avoid cross-device cache miss: @@ -1140,14 +1144,14 @@ async function buildPlannerMessages(rawUserInput) { // 3) Worldbook if (String(worldbook).trim()) messages.push({ role: 'system', content: worldbook }); - // 3.5) Story memory (小白X <剧情记忆> — fresh recall when available, stale cache as fallback) + // 4) Chat history (last 2 AI responses — floors N-1 & N-3) + if (String(recentChat).trim()) messages.push({ role: 'system', content: recentChat }); + + // 4.5) Story memory (小白X <剧情记忆> — after chat context, before plots) if (storySummary.trim()) { messages.push({ role: 'system', content: `\n${storySummary}\n` }); } - // 4) Chat history (last 2 AI responses — floors N-1 & N-3) - if (String(recentChat).trim()) messages.push({ role: 'system', content: recentChat }); - // 5) Vector recall — now merged into story_summary above, kept for compatibility // (vectorRaw is empty; this block intentionally does nothing) if (String(vector).trim()) messages.push({ role: 'system', content: vector }); From 19ffd157b446a3e3f2dc55214cc70640d2c09cd3 Mon Sep 17 00:00:00 2001 From: Hao19911125 <99091644+Hao19911125@users.noreply.github.com> Date: Sat, 28 Feb 2026 11:32:00 +0800 Subject: [PATCH 25/26] =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=8E=BB=20message=20?= =?UTF-8?q?=E5=B1=82=E7=BA=A7=E8=8E=B7=E5=8F=96=E5=8F=98=E9=87=8F+?= =?UTF-8?q?=E6=9A=B4=E9=9C=B2=E6=95=B4=E4=B8=AA=20vars=20=E5=AF=B9?= =?UTF-8?q?=E8=B1=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ena-planner/ena-planner.js | 34 ++++++++++++++++++------------ 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/modules/ena-planner/ena-planner.js b/modules/ena-planner/ena-planner.js index c925b3a..9734eab 100644 --- a/modules/ena-planner/ena-planner.js +++ b/modules/ena-planner/ena-planner.js @@ -716,18 +716,34 @@ async function buildWorldbookBlock(scanText) { * EJS rendering for worldbook entries * --------------------------*/ function getChatVariables() { - // Try multiple paths to get ST chat variables + // 1) Try chat-level variables (multiple paths) try { const ctx = getContextSafe(); - if (ctx?.chatMetadata?.variables) return ctx.chatMetadata.variables; + if (ctx?.chatMetadata?.variables && Object.keys(ctx.chatMetadata.variables).length) { + return ctx.chatMetadata.variables; + } } catch {} try { - if (window.chat_metadata?.variables) return window.chat_metadata.variables; + if (window.chat_metadata?.variables && Object.keys(window.chat_metadata.variables).length) { + return window.chat_metadata.variables; + } } catch {} try { const ctx = getContextSafe(); - if (ctx?.chat_metadata?.variables) return ctx.chat_metadata.variables; + if (ctx?.chat_metadata?.variables && Object.keys(ctx.chat_metadata.variables).length) { + return ctx.chat_metadata.variables; + } } catch {} + + // 2) Fallback: message-level variables (some presets store vars here instead) + try { + const msgVars = getLatestMessageVarTable(); + if (msgVars && typeof msgVars === 'object' && Object.keys(msgVars).length) { + console.log('[EnaPlanner] Chat-level vars empty, using message-level vars as fallback'); + return msgVars; + } + } catch {} + return {}; } @@ -761,17 +777,9 @@ function buildEjsContext() { return value; } - // Compute common derived values that entries might reference - const fire = Number(getvar('stat_data.蒂娜.火')) || 0; - const ice = Number(getvar('stat_data.蒂娜.冰')) || 0; - const dark = Number(getvar('stat_data.蒂娜.暗')) || 0; - const light = Number(getvar('stat_data.蒂娜.光')) || 0; - const maxAttrValue = Math.max(fire, ice, dark, light); - return { getvar, setvar, - fire, ice, dark, light, - maxAttrValue, + vars, Number, Math, JSON, String, Array, Object, parseInt, parseFloat, console: { log: () => {}, warn: () => {}, error: () => {} }, }; From 6e2b3dcdaa2f194af77e04d34e3be752e4b8f07e Mon Sep 17 00:00:00 2001 From: Hao19911125 <99091644+Hao19911125@users.noreply.github.com> Date: Sat, 28 Feb 2026 11:50:03 +0800 Subject: [PATCH 26/26] Update ena-planner.js --- modules/ena-planner/ena-planner.js | 62 +++++++++++++++++++----------- 1 file changed, 39 insertions(+), 23 deletions(-) diff --git a/modules/ena-planner/ena-planner.js b/modules/ena-planner/ena-planner.js index 9734eab..57b7aa9 100644 --- a/modules/ena-planner/ena-planner.js +++ b/modules/ena-planner/ena-planner.js @@ -716,35 +716,51 @@ async function buildWorldbookBlock(scanText) { * EJS rendering for worldbook entries * --------------------------*/ function getChatVariables() { - // 1) Try chat-level variables (multiple paths) - try { - const ctx = getContextSafe(); - if (ctx?.chatMetadata?.variables && Object.keys(ctx.chatMetadata.variables).length) { - return ctx.chatMetadata.variables; - } - } catch {} - try { - if (window.chat_metadata?.variables && Object.keys(window.chat_metadata.variables).length) { - return window.chat_metadata.variables; - } - } catch {} - try { - const ctx = getContextSafe(); - if (ctx?.chat_metadata?.variables && Object.keys(ctx.chat_metadata.variables).length) { - return ctx.chat_metadata.variables; - } - } catch {} + let vars = {}; - // 2) Fallback: message-level variables (some presets store vars here instead) + // 1) Chat-level variables + try { + const ctx = getContextSafe(); + if (ctx?.chatMetadata?.variables) vars = { ...ctx.chatMetadata.variables }; + } catch {} + if (!Object.keys(vars).length) { + try { + if (window.chat_metadata?.variables) vars = { ...window.chat_metadata.variables }; + } catch {} + } + if (!Object.keys(vars).length) { + try { + 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' && Object.keys(msgVars).length) { - console.log('[EnaPlanner] Chat-level vars empty, using message-level vars as fallback'); - return msgVars; + 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 {}; + return vars; } function buildEjsContext() {