Update local plugin changes
This commit is contained in:
@@ -43,7 +43,7 @@ const CONFIG_VERSION = 4;
|
||||
const MAX_SEED = 0xFFFFFFFF;
|
||||
const API_TEST_TIMEOUT = 15000;
|
||||
const PLACEHOLDER_REGEX = /\[image:([a-z0-9\-_]+)\]/gi;
|
||||
const INITIAL_RENDER_MESSAGE_LIMIT = 10;
|
||||
const INITIAL_RENDER_MESSAGE_LIMIT = 1;
|
||||
|
||||
const events = createModuleEvents(MODULE_KEY);
|
||||
|
||||
@@ -103,6 +103,7 @@ let settingsCache = null;
|
||||
let settingsLoaded = false;
|
||||
let generationAbortController = null;
|
||||
let messageObserver = null;
|
||||
let ensureNovelDrawPanelRef = null;
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// 样式
|
||||
@@ -177,6 +178,13 @@ function ensureStyles() {
|
||||
.xb-nd-edit-input:focus{border-color:rgba(212,165,116,0.5);outline:none}
|
||||
.xb-nd-edit-input.scene{border-color:rgba(212,165,116,0.3)}
|
||||
.xb-nd-edit-input.char{border-color:rgba(147,197,253,0.3)}
|
||||
.xb-nd-live-btn{position:absolute;bottom:10px;right:10px;z-index:5;padding:4px 8px;background:rgba(0,0,0,0.75);border:none;border-radius:12px;color:rgba(255,255,255,0.7);font-size:10px;font-weight:700;letter-spacing:0.5px;cursor:pointer;opacity:0.7;transition:all 0.2s;user-select:none}
|
||||
.xb-nd-live-btn:hover{opacity:1;background:rgba(0,0,0,0.85)}
|
||||
.xb-nd-live-btn.active{background:rgba(62,207,142,0.9);color:#fff;opacity:1;box-shadow:0 0 10px rgba(62,207,142,0.5)}
|
||||
.xb-nd-live-btn.loading{pointer-events:none;opacity:0.5}
|
||||
.xb-nd-img.mode-live .xb-nd-img-wrap>img{opacity:0!important;pointer-events:none}
|
||||
.xb-nd-live-canvas{border-radius:10px;overflow:hidden}
|
||||
.xb-nd-live-canvas canvas{display:block;border-radius:10px}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
@@ -770,6 +778,7 @@ function buildImageHtml({ slotId, imgId, url, tags, positive, messageId, state =
|
||||
<span class="xb-nd-nav-text">${displayVersion} / ${historyCount}</span>
|
||||
<button class="xb-nd-nav-arrow" data-action="nav-next" title="${currentIndex === 0 ? '重新生成' : '下一版本'}">›</button>
|
||||
</div>`;
|
||||
const liveBtn = `<button class="xb-nd-live-btn" data-action="toggle-live" title="Live Photo">LIVE</button>`;
|
||||
|
||||
const menuBusy = isBusy ? ' busy' : '';
|
||||
const menuHtml = `<div class="xb-nd-menu-wrap${menuBusy}">
|
||||
@@ -787,6 +796,7 @@ ${indicator}
|
||||
<div class="xb-nd-img-wrap" data-total="${historyCount}">
|
||||
<img src="${url}" style="max-width:100%;width:auto;height:auto;border-radius:10px;cursor:pointer;box-shadow:0 3px 15px rgba(0,0,0,0.25);${isBusy ? 'opacity:0.5;' : ''}" data-action="open-gallery" ${lazyAttr}>
|
||||
${navPill}
|
||||
${liveBtn}
|
||||
</div>
|
||||
${menuHtml}
|
||||
<div class="xb-nd-edit" style="display:none;position:absolute;bottom:8px;left:8px;right:8px;background:rgba(0,0,0,0.9);border-radius:10px;padding:10px;text-align:left;z-index:15;">
|
||||
@@ -856,6 +866,12 @@ function setImageState(container, state) {
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
async function navigateToImage(container, targetIndex) {
|
||||
try {
|
||||
const { destroyLiveEffect } = await import('./image-live-effect.js');
|
||||
destroyLiveEffect(container);
|
||||
container.querySelector('.xb-nd-live-btn')?.classList.remove('active');
|
||||
} catch {}
|
||||
|
||||
const slotId = container.dataset.slotId;
|
||||
const historyCount = parseInt(container.dataset.historyCount) || 1;
|
||||
const currentIndex = parseInt(container.dataset.currentIndex) || 0;
|
||||
@@ -966,6 +982,23 @@ function handleTouchEnd(e) {
|
||||
// 事件委托与图片操作
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
async function handleLiveToggle(container) {
|
||||
const btn = container.querySelector('.xb-nd-live-btn');
|
||||
if (!btn || btn.classList.contains('loading')) return;
|
||||
|
||||
btn.classList.add('loading');
|
||||
|
||||
try {
|
||||
const { toggleLiveEffect } = await import('./image-live-effect.js');
|
||||
const isActive = await toggleLiveEffect(container);
|
||||
btn.classList.remove('loading');
|
||||
btn.classList.toggle('active', isActive);
|
||||
} catch (e) {
|
||||
console.error('[NovelDraw] Live effect failed:', e);
|
||||
btn.classList.remove('loading');
|
||||
}
|
||||
}
|
||||
|
||||
function setupEventDelegation() {
|
||||
if (window._xbNovelEventsBound) return;
|
||||
window._xbNovelEventsBound = true;
|
||||
@@ -1045,6 +1078,10 @@ function setupEventDelegation() {
|
||||
else await refreshSingleImage(container);
|
||||
break;
|
||||
}
|
||||
case 'toggle-live': {
|
||||
handleLiveToggle(container);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}, { capture: true });
|
||||
|
||||
@@ -1268,6 +1305,12 @@ async function refreshSingleImage(container) {
|
||||
|
||||
if (!tags || currentState === ImageState.SAVING || currentState === ImageState.REFRESHING || !slotId) return;
|
||||
|
||||
try {
|
||||
const { destroyLiveEffect } = await import('./image-live-effect.js');
|
||||
destroyLiveEffect(container);
|
||||
container.querySelector('.xb-nd-live-btn')?.classList.remove('active');
|
||||
} catch {}
|
||||
|
||||
toggleEditPanel(container, false);
|
||||
setImageState(container, ImageState.REFRESHING);
|
||||
|
||||
@@ -1892,36 +1935,65 @@ async function generateAndInsertImages({ messageId, onStateChange }) {
|
||||
async function autoGenerateForLastAI() {
|
||||
const s = getSettings();
|
||||
if (!isModuleEnabled() || s.mode !== 'auto' || autoBusy) return;
|
||||
|
||||
const ctx = getContext();
|
||||
const chat = ctx.chat || [];
|
||||
const lastIdx = chat.length - 1;
|
||||
if (lastIdx < 0) return;
|
||||
|
||||
const lastMessage = chat[lastIdx];
|
||||
if (!lastMessage || lastMessage.is_user) return;
|
||||
|
||||
const content = String(lastMessage.mes || '').replace(PLACEHOLDER_REGEX, '').trim();
|
||||
if (content.length < 50) return;
|
||||
|
||||
lastMessage.extra ||= {};
|
||||
if (lastMessage.extra.xb_novel_auto_done) return;
|
||||
|
||||
autoBusy = true;
|
||||
|
||||
try {
|
||||
const { setState, FloatState } = await import('./floating-panel.js');
|
||||
const { setStateForMessage, FloatState, ensureNovelDrawPanel } = await import('./floating-panel.js');
|
||||
|
||||
// 确保面板存在
|
||||
const messageEl = document.querySelector(`.mes[mesid="${lastIdx}"]`);
|
||||
if (messageEl) {
|
||||
ensureNovelDrawPanel(messageEl, lastIdx, { force: true });
|
||||
}
|
||||
|
||||
await generateAndInsertImages({
|
||||
messageId: lastIdx,
|
||||
onStateChange: (state, data) => {
|
||||
switch (state) {
|
||||
case 'llm': setState(FloatState.LLM); break;
|
||||
case 'gen': setState(FloatState.GEN, data); break;
|
||||
case 'progress': setState(FloatState.GEN, data); break;
|
||||
case 'cooldown': setState(FloatState.COOLDOWN, data); break;
|
||||
case 'success': setState(data.success === data.total ? FloatState.SUCCESS : FloatState.PARTIAL, data); break;
|
||||
case 'llm':
|
||||
setStateForMessage(lastIdx, FloatState.LLM);
|
||||
break;
|
||||
case 'gen':
|
||||
case 'progress':
|
||||
setStateForMessage(lastIdx, FloatState.GEN, data);
|
||||
break;
|
||||
case 'cooldown':
|
||||
setStateForMessage(lastIdx, FloatState.COOLDOWN, data);
|
||||
break;
|
||||
case 'success':
|
||||
setStateForMessage(
|
||||
lastIdx,
|
||||
data.success === data.total ? FloatState.SUCCESS : FloatState.PARTIAL,
|
||||
data
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
lastMessage.extra.xb_novel_auto_done = true;
|
||||
|
||||
} catch (e) {
|
||||
console.error('[NovelDraw] 自动配图失败:', e);
|
||||
const { setState, FloatState } = await import('./floating-panel.js');
|
||||
setState(FloatState.ERROR, { error: classifyError(e) });
|
||||
try {
|
||||
const { setStateForMessage, FloatState } = await import('./floating-panel.js');
|
||||
setStateForMessage(lastIdx, FloatState.ERROR, { error: classifyError(e) });
|
||||
} catch {}
|
||||
} finally {
|
||||
autoBusy = false;
|
||||
}
|
||||
@@ -2363,6 +2435,22 @@ export async function openNovelDrawSettings() {
|
||||
showOverlay();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function renderExistingPanels() {
|
||||
if (typeof ensureNovelDrawPanelRef !== 'function') return;
|
||||
const context = getContext();
|
||||
const chat = context.chat || [];
|
||||
|
||||
chat.forEach((message, messageId) => {
|
||||
if (!message || message.is_user) return; // 跳过用户消息
|
||||
|
||||
const messageEl = document.querySelector(`.mes[mesid="${messageId}"]`);
|
||||
if (!messageEl) return;
|
||||
|
||||
ensureNovelDrawPanelRef(messageEl, messageId);
|
||||
});
|
||||
}
|
||||
|
||||
export async function initNovelDraw() {
|
||||
if (window?.isXiaobaixEnabled === false) return;
|
||||
|
||||
@@ -2374,10 +2462,51 @@ export async function initNovelDraw() {
|
||||
|
||||
setupEventDelegation();
|
||||
setupGenerateInterceptor();
|
||||
openDB().then(() => { const s = getSettings(); clearExpiredCache(s.cacheDays || 3); });
|
||||
openDB().then(() => {
|
||||
const s = getSettings();
|
||||
clearExpiredCache(s.cacheDays || 3);
|
||||
});
|
||||
|
||||
const { createFloatingPanel } = await import('./floating-panel.js');
|
||||
createFloatingPanel();
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
// 动态导入 floating-panel(避免循环依赖)
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
|
||||
const { ensureNovelDrawPanel: ensureNovelDrawPanelFn } = await import('./floating-panel.js');
|
||||
ensureNovelDrawPanelRef = ensureNovelDrawPanelFn;
|
||||
|
||||
// 为现有消息创建画图面板
|
||||
const renderExistingPanels = () => {
|
||||
const context = getContext();
|
||||
const chat = context.chat || [];
|
||||
|
||||
chat.forEach((message, messageId) => {
|
||||
if (!message || message.is_user) return;
|
||||
|
||||
const messageEl = document.querySelector(`.mes[mesid="${messageId}"]`);
|
||||
if (!messageEl) return;
|
||||
|
||||
ensureNovelDrawPanelRef?.(messageEl, messageId);
|
||||
});
|
||||
};
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
// 事件监听
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
|
||||
// AI 消息渲染时创建画图按钮
|
||||
events.on(event_types.CHARACTER_MESSAGE_RENDERED, (data) => {
|
||||
const messageId = typeof data === 'number' ? data : data?.messageId ?? data?.mesId;
|
||||
if (messageId === undefined) return;
|
||||
|
||||
const messageEl = document.querySelector(`.mes[mesid="${messageId}"]`);
|
||||
if (!messageEl) return;
|
||||
|
||||
const context = getContext();
|
||||
const message = context.chat?.[messageId];
|
||||
if (message?.is_user) return;
|
||||
|
||||
ensureNovelDrawPanelRef?.(messageEl, messageId);
|
||||
});
|
||||
|
||||
events.on(event_types.CHARACTER_MESSAGE_RENDERED, handleMessageRendered);
|
||||
events.on(event_types.USER_MESSAGE_RENDERED, handleMessageRendered);
|
||||
@@ -2385,7 +2514,28 @@ export async function initNovelDraw() {
|
||||
events.on(event_types.MESSAGE_EDITED, handleMessageModified);
|
||||
events.on(event_types.MESSAGE_UPDATED, handleMessageModified);
|
||||
events.on(event_types.MESSAGE_SWIPED, handleMessageModified);
|
||||
events.on(event_types.GENERATION_ENDED, async () => { try { await autoGenerateForLastAI(); } catch (e) { console.error('[NovelDraw]', e); } });
|
||||
events.on(event_types.GENERATION_ENDED, async () => {
|
||||
try {
|
||||
await autoGenerateForLastAI();
|
||||
} catch (e) {
|
||||
console.error('[NovelDraw]', e);
|
||||
}
|
||||
});
|
||||
|
||||
// 聊天切换时重新创建面板
|
||||
events.on(event_types.CHAT_CHANGED, () => {
|
||||
setTimeout(renderExistingPanels, 200);
|
||||
});
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
// 初始渲染
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
|
||||
renderExistingPanels();
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
// 全局 API
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
|
||||
window.xiaobaixNovelDraw = {
|
||||
getSettings,
|
||||
@@ -2437,8 +2587,16 @@ export async function cleanupNovelDraw() {
|
||||
window.removeEventListener('message', handleFrameMessage);
|
||||
document.getElementById('xiaobaix-novel-draw-overlay')?.remove();
|
||||
|
||||
const { destroyFloatingPanel } = await import('./floating-panel.js');
|
||||
destroyFloatingPanel();
|
||||
// 动态导入并清理
|
||||
try {
|
||||
const { destroyFloatingPanel } = await import('./floating-panel.js');
|
||||
destroyFloatingPanel();
|
||||
} catch {}
|
||||
|
||||
try {
|
||||
const { destroyAllLiveEffects } = await import('./image-live-effect.js');
|
||||
destroyAllLiveEffects();
|
||||
} catch {}
|
||||
|
||||
delete window.xiaobaixNovelDraw;
|
||||
delete window._xbNovelEventsBound;
|
||||
|
||||
Reference in New Issue
Block a user