/**
* 收藏/世界书管理
*/
import { requestSave } from './save-manager.js';
import { world_names, loadWorldInfo, saveWorldInfo } from '../../../world-info.js';
import { getSettings } from './config.js';
import { escapeHtml } from './utils.js';
import { getUserPersonaFromST } from './ui.js';
import { showToast } from './toast.js';
// 刷新收藏列表
export function refreshFavoritesList(filter = 'all') {
const settings = getSettings();
const listEl = document.getElementById('wechat-favorites-list');
if (!listEl) return;
const selectedLorebooks = settings.selectedLorebooks || [];
const userPersonas = settings.userPersonas || [];
let items = [];
// 用户设定
if (filter === 'all' || filter === 'user') {
userPersonas.forEach((persona, idx) => {
items.push({
type: 'user',
index: idx,
name: persona.name || '用户设定',
content: persona.content?.substring(0, 50) + '...' || '',
enabled: persona.enabled !== false,
time: persona.addedTime || ''
});
});
}
// 世界书
if (filter === 'all' || filter === 'global' || filter === 'character') {
selectedLorebooks.forEach((lb, idx) => {
// 过滤掉总结世界书(不在收藏中显示,只在历史回顾中显示)
const isSummaryBook = lb.fromSummary === true ||
(lb.name?.startsWith('【可乐】和') && lb.name?.endsWith('的聊天'));
if (isSummaryBook) return;
// 判断是否是角色卡自带的世界书
const isCharacterBook = lb.fromCharacter === true;
const itemType = isCharacterBook ? 'character' : 'global';
if (filter === 'all' || filter === itemType) {
items.push({
type: itemType,
index: idx,
name: lb.name,
content: `${lb.entries?.length || 0} 个条目`,
enabled: lb.enabled !== false,
time: lb.addedTime || '',
entriesCount: lb.entries?.length || 0
});
}
});
}
if (items.length === 0) {
// 根据当前筛选显示不同的空状态按钮
let emptyButtons = '';
let emptyIcon = '';
let emptyText = '暂无收藏内容';
if (filter === 'user') {
emptyIcon = '';
emptyText = '暂无用户设定';
emptyButtons = ``;
} else if (filter === 'character') {
emptyIcon = '';
emptyText = '暂无角色卡世界书';
emptyButtons = `
`;
} else if (filter === 'global') {
emptyIcon = '';
emptyText = '暂无全局世界书';
emptyButtons = ``;
} else {
emptyButtons = `
`;
}
listEl.innerHTML = `
`;
// 插入到列表项后面
targetItemEl.after(panel);
// 动画展开
requestAnimationFrame(() => {
panel.classList.add('wechat-lorebook-panel-show');
});
// 绑定事件
bindPersonaPanelEvents(panel, personaIdx);
}
// 关闭用户设定详情面板
export function closeUserPersonaDetail() {
const panel = document.getElementById('wechat-persona-expand-panel');
if (panel) {
panel.classList.remove('wechat-lorebook-panel-show');
setTimeout(() => panel.remove(), 200);
}
// 移除展开状态
const listEl = document.getElementById('wechat-favorites-list');
if (listEl) {
listEl.querySelectorAll('.wechat-favorites-item[data-type="user"]').forEach(el => {
el.classList.remove('wechat-favorites-item-expanded');
});
}
expandedPersonaIdx = null;
}
// 新建用户设定弹窗
function showNewPersonaModal() {
const settings = getSettings();
const modal = document.createElement('div');
modal.className = 'wechat-modal';
modal.id = 'wechat-persona-modal';
modal.innerHTML = `
${entries.length === 0 ? '
暂无条目
' :
entries.map((entry, idx) => `
${(entry.keys || []).map(k => `${escapeHtml(k)}`).join('')}
${escapeHtml(entry.content?.substring(0, 150) || '')}${entry.content?.length > 150 ? '...' : ''}
`).join('')
}
`;
// 插入到列表项后面
targetItemEl.after(panel);
// 动画展开
requestAnimationFrame(() => {
panel.classList.add('wechat-lorebook-panel-show');
});
// 绑定事件
bindLorebookPanelEvents(panel, lorebook, lorebookIdx);
}
// 关闭世界书详情面板
export function closeLorebookDetail() {
const panel = document.getElementById('wechat-lorebook-expand-panel');
if (panel) {
panel.classList.remove('wechat-lorebook-panel-show');
setTimeout(() => panel.remove(), 200);
}
// 移除展开状态
const listEl = document.getElementById('wechat-favorites-list');
if (listEl) {
listEl.querySelectorAll('.wechat-favorites-item-expanded').forEach(el => {
el.classList.remove('wechat-favorites-item-expanded');
});
}
expandedLorebookIdx = null;
}
// 绑定展开面板的事件
function bindLorebookPanelEvents(panel, lorebook, lorebookIdx) {
// 关闭/收起按钮
panel.querySelector('.wechat-lorebook-panel-close').addEventListener('click', closeLorebookDetail);
// 条目启用开关
panel.querySelectorAll('.wechat-entry-toggle').forEach((toggle) => {
toggle.addEventListener('change', async (e) => {
e.stopPropagation();
const entryItem = toggle.closest('.wechat-lorebook-entry-item');
const entryIdx = parseInt(entryItem.dataset.entryIndex);
const settings = getSettings();
if (settings.selectedLorebooks?.[lorebookIdx]?.entries?.[entryIdx]) {
settings.selectedLorebooks[lorebookIdx].entries[entryIdx].enabled = toggle.checked;
requestSave();
// 同步到酒馆
await syncLorebookToTavern(lorebook.name, lorebookIdx);
}
});
});
// 编辑按钮
panel.querySelectorAll('.wechat-entry-edit-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
const entryItem = btn.closest('.wechat-lorebook-entry-item');
const editForm = entryItem.querySelector('.wechat-lorebook-entry-edit-form');
const preview = entryItem.querySelector('.wechat-lorebook-entry-preview');
const keysDiv = entryItem.querySelector('.wechat-lorebook-entry-keys');
// 切换显示
editForm.classList.toggle('hidden');
if (!editForm.classList.contains('hidden')) {
preview.classList.add('hidden');
keysDiv.classList.add('hidden');
btn.textContent = '📝';
} else {
preview.classList.remove('hidden');
keysDiv.classList.remove('hidden');
btn.textContent = '✏️';
}
});
});
// 取消编辑按钮
panel.querySelectorAll('.wechat-entry-cancel-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
const entryItem = btn.closest('.wechat-lorebook-entry-item');
const editForm = entryItem.querySelector('.wechat-lorebook-entry-edit-form');
const preview = entryItem.querySelector('.wechat-lorebook-entry-preview');
const keysDiv = entryItem.querySelector('.wechat-lorebook-entry-keys');
const editBtn = entryItem.querySelector('.wechat-entry-edit-btn');
editForm.classList.add('hidden');
preview.classList.remove('hidden');
keysDiv.classList.remove('hidden');
editBtn.textContent = '✏️';
});
});
// 保存编辑按钮
panel.querySelectorAll('.wechat-entry-save-btn').forEach(btn => {
btn.addEventListener('click', async (e) => {
e.stopPropagation();
const entryItem = btn.closest('.wechat-lorebook-entry-item');
const entryIdx = parseInt(entryItem.dataset.entryIndex);
const comment = entryItem.querySelector('.wechat-entry-comment').value.trim();
const keysInput = entryItem.querySelector('.wechat-entry-keys-input').value;
const content = entryItem.querySelector('.wechat-entry-content-input').value;
// 解析关键词
const keys = keysInput.split(/[,,]/).map(k => k.trim()).filter(k => k);
const settings = getSettings();
if (settings.selectedLorebooks?.[lorebookIdx]?.entries?.[entryIdx]) {
const entry = settings.selectedLorebooks[lorebookIdx].entries[entryIdx];
entry.comment = comment;
entry.keys = keys;
entry.content = content;
requestSave();
// 同步到酒馆
btn.disabled = true;
btn.textContent = '同步中...';
try {
await syncLorebookToTavern(lorebook.name, lorebookIdx);
showToast('已保存并同步到酒馆');
// 更新UI显示
const titleEl = entryItem.querySelector('.wechat-lorebook-entry-title');
titleEl.textContent = comment || keys[0] || '条目' + (entryIdx + 1);
const keysDiv = entryItem.querySelector('.wechat-lorebook-entry-keys');
keysDiv.innerHTML = keys.map(k => `
导入全局世界书
从酒馆世界书列表中选择要导入的世界书,导入后将作为全局世界书供所有角色共享使用
${availableWorlds.length === 0 ? '
暂无可用世界书
请先在酒馆中创建世界书
' :
availableWorlds.map(name => `
${escapeHtml(name)}
${selectedNames.includes(name) ? '✓ 已导入' : '+ 导入'}
`).join('')
}
`;
document.body.appendChild(modal);
// 关闭按钮
modal.querySelector('#wechat-lorebook-modal-close').addEventListener('click', () => modal.remove());
// 点击背景关闭
modal.addEventListener('click', (e) => {
if (e.target === modal) modal.remove();
});
// 绑定世界书点击
modal.querySelectorAll('.wechat-lorebook-item').forEach(item => {
item.addEventListener('click', async () => {
const name = item.dataset.name;
await addLorebookToFavorites(name);
modal.remove();
refreshFavoritesList();
});
});
}
// 显示添加用户设定弹窗
export function showAddPersonaPanel() {
// 移除已有弹窗
document.getElementById('wechat-add-persona-modal')?.remove();
const modal = document.createElement('div');
modal.className = 'wechat-modal';
modal.id = 'wechat-add-persona-modal';
modal.innerHTML = `
加载中...
';
modal.classList.remove('hidden');
try {
const availableWorlds = typeof world_names !== 'undefined' ? world_names : [];
const settings = getSettings();
const selectedNames = (settings.selectedLorebooks || []).map(lb => lb.name);
if (availableWorlds.length === 0) {
listEl.innerHTML = '