diff --git a/README.md b/README.md index f650a53..023cafa 100644 --- a/README.md +++ b/README.md @@ -5,66 +5,81 @@ SillyTavern 扩展插件 - 小白X ## 📁 目录结构 ``` -LittleWhiteBox/ -├── manifest.json # 插件配置清单 -├── index.js # 主入口文件 -├── settings.html # 设置页面模板 -├── style.css # 全局样式 -│ -├── modules/ # 功能模块目录 -│ ├── streaming-generation.js # 流式生成 -│ ├── dynamic-prompt.js # 动态提示词 -│ ├── immersive-mode.js # 沉浸模式 -│ ├── message-preview.js # 消息预览 -│ ├── wallhaven-background.js # 壁纸背景 -│ ├── button-collapse.js # 按钮折叠 -│ ├── control-audio.js # 音频控制 -│ ├── script-assistant.js # 脚本助手 -│ │ -│ ├── variables/ # 变量系统 -│ │ ├── variables-core.js -│ │ └── variables-panel.js -│ │ -│ ├── template-editor/ # 模板编辑器 -│ │ ├── template-editor.js -│ │ └── template-editor.html -│ │ -│ ├── scheduled-tasks/ # 定时任务 -│ │ ├── scheduled-tasks.js -│ │ ├── scheduled-tasks.html -│ │ └── embedded-tasks.html -│ │ -│ ├── story-summary/ # 故事摘要 -│ │ ├── story-summary.js -│ │ └── story-summary.html -│ │ -│ └── story-outline/ # 故事大纲 -│ ├── story-outline.js -│ ├── story-outline-prompt.js -│ └── story-outline.html -│ -├── bridges/ # 外部桥接模块 -│ ├── worldbook-bridge.js # 世界书桥接 -│ ├── call-generate-service.js # 生成服务调用 -│ └── wrapper-iframe.js # iframe 包装器 -│ -├── ui/ # UI 模板 -│ └── character-updater-menus.html -│ -└── docs/ # 文档 - ├── script-docs.md # 脚本文档 - ├── LICENSE.md # 许可证 - ├── COPYRIGHT # 版权信息 - └── NOTICE # 声明 +LittleWhiteBox/ +├── index.js # 主入口,初始化所有模块,管理总开关 +├── manifest.json # 插件清单,版本、依赖声明 +├── settings.html # 主设置页面,所有模块开关UI +├── style.css # 全局样式 +├── README.md # 说明文档 +│ +├── core/ # 核心公共模块 +│ ├── constants.js # 共享常量 EXT_ID, extensionFolderPath +│ ├── event-manager.js # 统一事件管理,createModuleEvents() +│ ├── debug-core.js # 日志 xbLog + 缓存注册 CacheRegistry +│ ├── slash-command.js # 斜杠命令执行封装 +│ ├── variable-path.js # 变量路径解析工具 +│ └── server-storage.js # 服务器文件存储,防抖保存,自动重试 +│ +├── modules/ # 功能模块 +│ ├── button-collapse.js # 按钮折叠,消息区按钮收纳 +│ ├── control-audio.js # 音频控制,iframe音频权限 +│ ├── iframe-renderer.js # iframe渲染,代码块转交互界面 +│ ├── immersive-mode.js # 沉浸模式,界面布局优化 +│ ├── message-preview.js # 消息预览,Log记录/拦截 +│ ├── script-assistant.js # 脚本助手,AI写卡知识注入 +│ ├── streaming-generation.js # 流式生成,xbgenraw命令 +│ │ +│ ├── debug-panel/ # 调试面板模块 +│ │ ├── debug-panel.js # 悬浮窗控制,父子通信,懒加载 +│ │ └── debug-panel.html # 三Tab界面:日志/事件/缓存 +│ │ +│ ├── fourth-wall/ # 四次元壁模块(皮下交流) +│ │ ├── fourth-wall.js # 悬浮按钮,postMessage通讯 +│ │ └── fourth-wall.html # iframe聊天界面,提示词编辑 +│ │ +│ ├── novel-draw/ # Novel画图模块 +│ │ ├── novel-draw.js # NovelAI画图,预设管理,LLM场景分析 +│ │ ├── novel-draw.html # 参数配置,图片管理(画廊+缓存) +│ │ ├── floating-panel.js # 悬浮面板,状态显示,快捷操作 +│ │ └── gallery-cache.js # IndexedDB缓存,小画廊UI +│ │ +│ ├── scheduled-tasks/ # 定时任务模块 +│ │ ├── scheduled-tasks.js # 全局/角色/预设任务调度 +│ │ ├── scheduled-tasks.html # 任务设置面板 +│ │ └── embedded-tasks.html # 嵌入式任务界面 +│ │ +│ ├── template-editor/ # 模板编辑器模块 +│ │ ├── template-editor.js # 沉浸式模板,流式多楼层渲染 +│ │ └── template-editor.html # 模板编辑界面 +│ │ +│ ├── story-outline/ # 故事大纲模块 +│ │ ├── story-outline.js # 可视化剧情地图 +│ │ ├── story-outline.html # 大纲编辑界面 +│ │ └── story-outline-prompt.js # 大纲生成提示词 +│ │ +│ ├── story-summary/ # 剧情总结模块 +│ │ ├── story-summary.js # 增量总结,时间线,关系图 +│ │ └── story-summary.html # 总结面板界面 +│ │ +│ └── variables/ # 变量系统模块 +│ ├── var-commands.js # /xbgetvar /xbsetvar 命令,宏替换 +│ ├── varevent-editor.js # 条件规则编辑器,varevent运行时 +│ ├── variables-core.js # plot-log解析,快照回滚,变量守护 +│ └── variables-panel.js # 变量面板UI +│ +├── bridges/ # 外部服务桥接 +│ ├── call-generate-service.js # 父窗口:调用ST生成服务 +│ ├── worldbook-bridge.js # 父窗口:世界书读写桥接 +│ └── wrapper-iframe.js # iframe内部:提供CallGenerate API +│ +└── docs/ # 文档与许可 + ├── script-docs.md # 脚本文档 + ├── COPYRIGHT # 版权声明 + ├── LICENSE.md # 许可证 + └── NOTICE # 通知 + ``` -## 📝 模块组织规则 - -- **单文件模块**:直接放在 `modules/` 目录下 -- **多文件模块**:创建子目录,包含相关的 JS、HTML 等文件 -- **桥接模块**:与外部系统交互的独立模块放在 `bridges/` -- **避免使用 `index.js`**:每个模块文件直接命名,不使用 `index.js` - ## 🔄 版本历史 - v2.2.2 - 目录结构重构(2025-12-08) diff --git a/core/server-storage.js b/core/server-storage.js index dae0c27..e7bfdbc 100644 --- a/core/server-storage.js +++ b/core/server-storage.js @@ -137,3 +137,4 @@ class StorageFile { export const TasksStorage = new StorageFile('LittleWhiteBox_Tasks.json'); export const StoryOutlineStorage = new StorageFile('LittleWhiteBox_StoryOutline.json'); +export const NovelDrawStorage = new StorageFile('LittleWhiteBox_NovelDraw.json', { debounceMs: 800 }); \ No newline at end of file diff --git a/index.js b/index.js index c86c989..87576a1 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,3 @@ -// =========================================================================== -// Imports -// =========================================================================== - import { extension_settings, getContext } from "../../../extensions.js"; import { saveSettingsDebounced, eventSource, event_types, getRequestHeaders } from "../../../../script.js"; import { EXT_ID, EXT_NAME, extensionFolderPath } from "./core/constants.js"; @@ -12,7 +8,6 @@ import { initScriptAssistant } from "./modules/script-assistant.js"; import { initMessagePreview, addHistoryButtonsDebounced } from "./modules/message-preview.js"; import { initImmersiveMode } from "./modules/immersive-mode.js"; import { initTemplateEditor, templateSettings } from "./modules/template-editor/template-editor.js"; -import { initWallhavenBackground } from "./modules/wallhaven-background.js"; import { initFourthWall, fourthWallCleanup } from "./modules/fourth-wall/fourth-wall.js"; import { initButtonCollapse } from "./modules/button-collapse.js"; import { initVariablesPanel, getVariablesPanelInstance, cleanupVariablesPanel } from "./modules/variables/variables-panel.js"; @@ -35,10 +30,6 @@ import { initNovelDraw, cleanupNovelDraw } from "./modules/novel-draw/novel-draw import "./modules/story-summary/story-summary.js"; import "./modules/story-outline/story-outline.js"; -// =========================================================================== -// Constants and Default Settings -// =========================================================================== - const MODULE_NAME = "xiaobaix-memory"; extension_settings[EXT_ID] = extension_settings[EXT_ID] || { @@ -49,7 +40,6 @@ extension_settings[EXT_ID] = extension_settings[EXT_ID] || { tasks: { enabled: true, globalTasks: [], processedMessages: [], character_allowed_tasks: [] }, scriptAssistant: { enabled: false }, preview: { enabled: false }, - wallhaven: { enabled: false }, immersive: { enabled: false }, fourthWall: { enabled: false }, audio: { enabled: true }, @@ -67,10 +57,6 @@ extension_settings[EXT_ID] = extension_settings[EXT_ID] || { const settings = extension_settings[EXT_ID]; if (settings.dynamicPrompt && !settings.fourthWall) settings.fourthWall = settings.dynamicPrompt; -// =========================================================================== -// Deprecated Data Cleanup -// =========================================================================== - const DEPRECATED_KEYS = [ 'characterUpdater', 'promptSections', @@ -97,10 +83,6 @@ function cleanupDeprecatedData() { } } -// =========================================================================== -// State Variables -// =========================================================================== - let isXiaobaixEnabled = settings.enabled; let moduleCleanupFunctions = new Map(); let updateCheckPerformed = false; @@ -117,10 +99,6 @@ window.testRemoveUpdateUI = () => { removeAllUpdateNotices(); }; -// =========================================================================== -// Update Check -// =========================================================================== - async function checkLittleWhiteBoxUpdate() { try { const timestamp = Date.now(); @@ -246,10 +224,6 @@ async function performExtensionUpdateCheck() { } catch (error) {} } -// =========================================================================== -// Module Cleanup Registration -// =========================================================================== - function registerModuleCleanup(moduleName, cleanupFunction) { moduleCleanupFunctions.set(moduleName, cleanupFunction); } @@ -283,22 +257,9 @@ function cleanupAllResources() { btn.style.display = 'none'; } }); - document.getElementById('xiaobaix-hide-code')?.remove(); - document.body.classList.remove('xiaobaix-active'); - document.querySelectorAll('pre[data-xiaobaix-bound="true"]').forEach(pre => { - pre.classList.remove('xb-show'); - pre.removeAttribute('data-xbfinal'); - delete pre.dataset.xbFinal; - pre.style.display = ''; - delete pre.dataset.xiaobaixBound; - }); removeSkeletonStyles(); } -// =========================================================================== -// Utility Functions -// =========================================================================== - async function waitForElement(selector, root = document, timeout = 10000) { const start = Date.now(); while (Date.now() - start < timeout) { @@ -309,16 +270,10 @@ async function waitForElement(selector, root = document, timeout = 10000) { return null; } -// =========================================================================== -// Settings Controls Toggle -// =========================================================================== - function toggleSettingsControls(enabled) { const controls = [ 'xiaobaix_sandbox', 'xiaobaix_recorded_enabled', 'xiaobaix_preview_enabled', 'xiaobaix_script_assistant', 'scheduled_tasks_enabled', 'xiaobaix_template_enabled', - 'wallhaven_enabled', 'wallhaven_bg_mode', 'wallhaven_category', - 'wallhaven_purity', 'wallhaven_opacity', 'xiaobaix_immersive_enabled', 'xiaobaix_fourth_wall_enabled', 'xiaobaix_audio_enabled', 'xiaobaix_variables_panel_enabled', 'xiaobaix_use_blob', 'xiaobaix_variables_core_enabled', 'Wrapperiframe', 'xiaobaix_render_enabled', @@ -339,37 +294,8 @@ function toggleSettingsControls(enabled) { } } -function ensureHideCodeStyle(enable) { - const id = 'xiaobaix-hide-code'; - const old = document.getElementById(id); - if (!enable) { - old?.remove(); - return; - } - if (old) return; - const hideCodeStyle = document.createElement('style'); - hideCodeStyle.id = id; - hideCodeStyle.textContent = ` - .xiaobaix-active .mes_text pre { display: none !important; } - .xiaobaix-active .mes_text pre.xb-show { display: block !important; } - `; - document.head.appendChild(hideCodeStyle); -} - -function setActiveClass(enable) { - document.body.classList.toggle('xiaobaix-active', !!enable); -} - -// =========================================================================== -// Toggle All Features -// =========================================================================== - async function toggleAllFeatures(enabled) { if (enabled) { - if (settings.renderEnabled !== false) { - ensureHideCodeStyle(true); - setActiveClass(true); - } toggleSettingsControls(true); try { window.XB_applyPrevStates && window.XB_applyPrevStates(); } catch (e) {} saveSettingsDebounced(); @@ -383,7 +309,6 @@ async function toggleAllFeatures(enabled) { { condition: extension_settings[EXT_ID].scriptAssistant?.enabled, init: initScriptAssistant }, { condition: extension_settings[EXT_ID].immersive?.enabled, init: initImmersiveMode }, { condition: extension_settings[EXT_ID].templateEditor?.enabled, init: initTemplateEditor }, - { condition: extension_settings[EXT_ID].wallhaven?.enabled, init: initWallhavenBackground }, { condition: extension_settings[EXT_ID].fourthWall?.enabled, init: initFourthWall }, { condition: extension_settings[EXT_ID].variablesPanel?.enabled, init: initVariablesPanel }, { condition: extension_settings[EXT_ID].variablesCore?.enabled, init: initVariablesCore }, @@ -426,15 +351,6 @@ async function toggleAllFeatures(enabled) { try { cleanupNovelDraw(); } catch (e) {} try { clearBlobCaches(); } catch (e) {} toggleSettingsControls(false); - document.getElementById('xiaobaix-hide-code')?.remove(); - setActiveClass(false); - document.querySelectorAll('pre[data-xiaobaix-bound="true"]').forEach(pre => { - pre.classList.remove('xb-show'); - pre.removeAttribute('data-xbfinal'); - delete pre.dataset.xbFinal; - pre.style.display = ''; - delete pre.dataset.xiaobaixBound; - }); window.removeScriptDocs?.(); try { window.cleanupWorldbookHostBridge && window.cleanupWorldbookHostBridge(); document.getElementById('xb-worldbook')?.remove(); } catch (e) {} try { window.cleanupCallGenerateHostBridge && window.cleanupCallGenerateHostBridge(); document.getElementById('xb-callgen')?.remove(); } catch (e) {} @@ -443,10 +359,6 @@ async function toggleAllFeatures(enabled) { } } -// =========================================================================== -// Settings Panel Setup -// =========================================================================== - async function setupSettings() { try { const settingsContainer = await waitForElement("#extensions_settings"); @@ -483,7 +395,6 @@ async function setupSettings() { { id: 'xiaobaix_script_assistant', key: 'scriptAssistant', init: initScriptAssistant }, { id: 'scheduled_tasks_enabled', key: 'tasks', init: initTasks }, { id: 'xiaobaix_template_enabled', key: 'templateEditor', init: initTemplateEditor }, - { id: 'wallhaven_enabled', key: 'wallhaven', init: initWallhavenBackground }, { id: 'xiaobaix_fourth_wall_enabled', key: 'fourthWall', init: initFourthWall }, { id: 'xiaobaix_variables_panel_enabled', key: 'variablesPanel', init: initVariablesPanel }, { id: 'xiaobaix_variables_core_enabled', key: 'variablesCore', init: initVariablesCore }, @@ -550,12 +461,9 @@ async function setupSettings() { settings.renderEnabled = $(this).prop("checked"); saveSettingsDebounced(); if (!settings.renderEnabled && wasEnabled) { - document.getElementById('xiaobaix-hide-code')?.remove(); - document.body.classList.remove('xiaobaix-active'); - invalidateAll(); + cleanupRenderer(); } else if (settings.renderEnabled && !wasEnabled) { - ensureHideCodeStyle(true); - document.body.classList.add('xiaobaix-active'); + initRenderer(); setTimeout(() => processExistingMessages(), 100); } }); @@ -588,14 +496,13 @@ async function setupSettings() { scriptAssistant: 'xiaobaix_script_assistant', tasks: 'scheduled_tasks_enabled', templateEditor: 'xiaobaix_template_enabled', - wallhaven: 'wallhaven_enabled', fourthWall: 'xiaobaix_fourth_wall_enabled', variablesPanel: 'xiaobaix_variables_panel_enabled', variablesCore: 'xiaobaix_variables_core_enabled', novelDraw: 'xiaobaix_novel_draw_enabled' }; const ON = ['templateEditor', 'tasks', 'variablesCore', 'audio', 'storySummary', 'recorded']; - const OFF = ['preview', 'scriptAssistant', 'immersive', 'wallhaven', 'variablesPanel', 'fourthWall', 'storyOutline', 'novelDraw']; + const OFF = ['preview', 'scriptAssistant', 'immersive', 'variablesPanel', 'fourthWall', 'storyOutline', 'novelDraw']; function setChecked(id, val) { const el = document.getElementById(id); if (el) { @@ -648,10 +555,6 @@ function setupDebugButtonInSettings() { } catch (e) {} } -// =========================================================================== -// Menu Tabs -// =========================================================================== - function setupMenuTabs() { $(document).on('click', '.menu-tab', function () { const targetId = $(this).attr('data-target'); @@ -668,31 +571,18 @@ function setupMenuTabs() { }, 300); } -// =========================================================================== -// Global Exports -// =========================================================================== - window.processExistingMessages = processExistingMessages; window.renderHtmlInIframe = renderHtmlInIframe; window.registerModuleCleanup = registerModuleCleanup; window.updateLittleWhiteBoxExtension = updateLittleWhiteBoxExtension; window.removeAllUpdateNotices = removeAllUpdateNotices; -// =========================================================================== -// Entry Point -// =========================================================================== - jQuery(async () => { try { cleanupDeprecatedData(); isXiaobaixEnabled = settings.enabled; window.isXiaobaixEnabled = isXiaobaixEnabled; - if (isXiaobaixEnabled && settings.renderEnabled !== false) { - ensureHideCodeStyle(true); - setActiveClass(true); - } - if (!document.getElementById('xiaobaix-skeleton-style')) { const skelStyle = document.createElement('style'); skelStyle.id = 'xiaobaix-skeleton-style'; @@ -739,7 +629,6 @@ jQuery(async () => { { condition: settings.scriptAssistant?.enabled, init: initScriptAssistant }, { condition: settings.immersive?.enabled, init: initImmersiveMode }, { condition: settings.templateEditor?.enabled, init: initTemplateEditor }, - { condition: settings.wallhaven?.enabled, init: initWallhavenBackground }, { condition: settings.fourthWall?.enabled, init: initFourthWall }, { condition: settings.variablesPanel?.enabled, init: initVariablesPanel }, { condition: settings.variablesCore?.enabled, init: initVariablesCore }, diff --git a/manifest.json b/manifest.json index ad6ee22..e78e91b 100644 --- a/manifest.json +++ b/manifest.json @@ -7,6 +7,6 @@ "css": "style.css", "author": "biex", "version": "2.3.1", - "homePage": "https://github.com/RT15548/LittleWhiteBox" - -} + "homePage": "https://github.com/RT15548/LittleWhiteBox" , + "generate_interceptor": "xiaobaixGenerateInterceptor" +} \ No newline at end of file diff --git a/modules/fourth-wall/fourth-wall.html b/modules/fourth-wall/fourth-wall.html index 97fdc20..bd86a8e 100644 --- a/modules/fourth-wall/fourth-wall.html +++ b/modules/fourth-wall/fourth-wall.html @@ -199,17 +199,93 @@ html, body { .fw-streaming { opacity: 0.8; font-style: italic; } .fw-empty { text-align: center; color: var(--text-muted); padding: 40px; font-size: 0.875rem; } -.fw-img-slot { margin: 8px 0; } -.fw-img-slot img { max-width: min(300px, 70vw); max-height: 50vh; border-radius: 8px; display: block; } -.fw-img-loading { font-size: 0.75rem; color: var(--text-muted); display: flex; align-items: center; gap: 6px; } - -.fw-img-error { - width: 200px; height: 140px; background: var(--bg-tertiary); - border: 1px dashed var(--border-color); border-radius: 8px; - display: flex; flex-direction: column; align-items: center; justify-content: center; - color: var(--text-muted); font-size: 0.75rem; +/* 图片懒加载样式 */ +.fw-img-slot { + margin: 8px 0; + min-height: 80px; + position: relative; } +.fw-img-slot img { + max-width: min(300px, 70vw); + max-height: 50vh; + border-radius: 8px; + display: block; + cursor: pointer; + transition: opacity 0.2s; +} + +.fw-img-slot img:hover { opacity: 0.9; } + +.fw-img-placeholder { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 8px; + padding: 24px 16px; + background: var(--bg-tertiary); + border: 1px dashed var(--border-color); + border-radius: 8px; + color: var(--text-muted); + font-size: 0.75rem; +} + +.fw-img-placeholder i { font-size: 24px; opacity: 0.4; } + +.fw-img-loading { + display: flex; + align-items: center; + gap: 8px; + padding: 16px 20px; + background: linear-gradient(135deg, rgba(76,154,255,0.08), rgba(118,75,162,0.08)); + border: 1px solid rgba(76,154,255,0.15); + border-radius: 8px; + color: var(--text-secondary); + font-size: 0.8125rem; +} + +.fw-img-error { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + padding: 16px; + background: rgba(248,113,113,0.08); + border: 1px dashed rgba(248,113,113,0.25); + border-radius: 8px; + color: #f87171; + font-size: 0.75rem; + text-align: center; +} + +.fw-img-retry { + margin-top: 4px; + padding: 4px 12px; + background: rgba(248,113,113,0.15); + border: 1px solid rgba(248,113,113,0.25); + border-radius: 4px; + color: #f87171; + font-size: 0.7rem; + cursor: pointer; + transition: all 0.2s; +} + +.fw-img-retry:hover { background: rgba(248,113,113,0.25); } + +.fw-img-badge { + position: absolute; + top: 4px; + right: 4px; + background: rgba(0,0,0,0.6); + color: #fbbf24; + font-size: 10px; + padding: 3px 6px; + border-radius: 4px; + backdrop-filter: blur(4px); +} + +/* 语音样式 */ .fw-voice-bubble { display: inline-flex; align-items: center; gap: 10px; padding: 10px 16px; background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%); @@ -331,7 +407,6 @@ html, body { .fw-prompt-hint { font-size: 0.75rem; color: var(--text-muted); margin-top: 4px; } -/* 思考折叠UI - 一体化卡片设计 */ .fw-thinking-card { margin-bottom: 6px; background: rgba(0,0,0,0.03); @@ -363,9 +438,7 @@ html, body { transition: transform 0.2s; } -.fw-thinking-header.expanded .chevron { - transform: rotate(90deg); -} +.fw-thinking-header.expanded .chevron { transform: rotate(90deg); } .fw-thinking-body { display: none; @@ -391,7 +464,6 @@ html, body { .fw-thinking-body::-webkit-scrollbar { width: 4px; } .fw-thinking-body::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.15); border-radius: 2px; } -/* 流式思考指示器 */ .fw-thinking-header.streaming span::after { content: ''; display: inline-block; @@ -408,18 +480,9 @@ html, body { 50% { opacity: 1; } } -/* 编辑模式宽度最大化 */ -.fw-row.editing { - max-width: 100%; -} - -.fw-row.editing .fw-bubble { - width: 100%; -} - -.fw-row.editing .fw-edit-area { - min-height: 80px; -} +.fw-row.editing { max-width: 100%; } +.fw-row.editing .fw-bubble { width: 100%; } +.fw-row.editing .fw-edit-area { min-height: 80px; } @media (max-width: 600px) { .fw-header { padding: 6px 12px; } @@ -431,6 +494,12 @@ html, body { .fw-bubble { padding: 8px 12px; font-size: 0.875rem; } .fw-avatar { width: 32px; height: 32px; } } + +@media (max-width: 480px) { + .fw-container { padding: 0; } + .fw-title { font-size: 0.875rem; } + .fw-btn { padding: 4px 8px; font-size: 0.7rem; } +} @@ -489,16 +558,9 @@ html, body {