Add files via upload

This commit is contained in:
Cola-Echo
2025-12-22 02:41:32 +08:00
committed by GitHub
commit 1e1bf1bab2
30 changed files with 34640 additions and 0 deletions

313
history-logs.js Normal file
View File

@@ -0,0 +1,313 @@
/**
* 历史回顾和日志功能
*/
import { saveSettingsDebounced } from '../../../../script.js';
import { getSettings, LOREBOOK_NAME_PREFIX, LOREBOOK_NAME_SUFFIX } from './config.js';
import { escapeHtml } from './utils.js';
import { showToast } from './toast.js';
// 最大日志数量
const MAX_LOGS = 20;
// 获取错误日志
export function getErrorLogs() {
const settings = getSettings();
return settings.errorLogs || [];
}
// 添加错误日志
export function addErrorLog(error, context = '') {
const settings = getSettings();
if (!settings.errorLogs) settings.errorLogs = [];
const now = new Date();
const timeStr = now.getHours().toString().padStart(2,'0') + ':' + now.getMinutes().toString().padStart(2,'0') + ':' + now.getSeconds().toString().padStart(2,'0');
// 生成简短的错误摘要约15字
const errorMsg = error?.message || String(error);
let summary = context ? context + ': ' : '';
// 截取关键信息
if (errorMsg.length > 15 - summary.length) {
summary += errorMsg.substring(0, 15 - summary.length) + '...';
} else {
summary += errorMsg;
}
const logEntry = {
time: timeStr,
summary: summary.substring(0, 18), // 确保不超过18字
message: errorMsg,
context: context
};
settings.errorLogs.unshift(logEntry);
// 只保留最近的 MAX_LOGS 条
if (settings.errorLogs.length > MAX_LOGS) {
settings.errorLogs = settings.errorLogs.slice(0, MAX_LOGS);
}
saveSettingsDebounced();
return logEntry;
}
// 清空错误日志
export function clearErrorLogs() {
const settings = getSettings();
settings.errorLogs = [];
saveSettingsDebounced();
}
// 刷新日志列表显示
export function refreshLogsList() {
const listEl = document.getElementById('wechat-logs-list');
if (!listEl) return;
const logs = getErrorLogs();
if (logs.length === 0) {
listEl.innerHTML = '<div style="text-align: center; color: var(--wechat-text-secondary); padding: 20px;">暂无错误日志 ✅</div>';
return;
}
listEl.innerHTML = logs.map((log, idx) => `
<div class="wechat-log-item" style="padding: 8px 10px; border-bottom: 1px solid var(--wechat-border); ${idx === 0 ? 'background: rgba(255,77,79,0.08);' : ''}">
<div style="display: flex; justify-content: space-between; align-items: center;">
<span style="color: #ff4d4f; font-weight: 500;">${escapeHtml(log.summary || log.message?.substring(0, 15) + '...')}</span>
<span style="color: var(--wechat-text-secondary); font-size: 11px;">${escapeHtml(log.time)}</span>
</div>
${log.message && log.message !== log.summary ? `<div style="margin-top: 4px; font-size: 11px; color: var(--wechat-text-secondary); word-break: break-all;">${escapeHtml(log.message.substring(0, 80))}${log.message.length > 80 ? '...' : ''}</div>` : ''}
</div>
`).join('');
}
// 判断世界书是否是总结生成的
function isSummaryLorebook(lorebook) {
// 检查名称格式【可乐】和xxx的聊天
if (lorebook.name?.startsWith(LOREBOOK_NAME_PREFIX) && lorebook.name?.endsWith(LOREBOOK_NAME_SUFFIX)) {
return true;
}
// 检查标记
if (lorebook.fromSummary === true) {
return true;
}
return false;
}
// 判断是否是群聊总结
function isGroupSummary(lorebook) {
// 从名称中提取人名部分
if (!lorebook.name?.startsWith(LOREBOOK_NAME_PREFIX)) return false;
const nameContent = lorebook.name.slice(LOREBOOK_NAME_PREFIX.length, -LOREBOOK_NAME_SUFFIX.length);
// 如果包含逗号,说明是多人(群聊)
return nameContent.includes(',') || nameContent.includes('');
}
// 获取总结世界书列表(按类型分类)
export function getSummaryLorebooks(filter = 'all') {
const settings = getSettings();
const selectedLorebooks = settings.selectedLorebooks || [];
const summaryBooks = selectedLorebooks
.map((lb, idx) => ({ ...lb, originalIndex: idx }))
.filter(lb => isSummaryLorebook(lb));
if (filter === 'all') {
return summaryBooks;
} else if (filter === 'contact') {
return summaryBooks.filter(lb => !isGroupSummary(lb));
} else if (filter === 'group') {
return summaryBooks.filter(lb => isGroupSummary(lb));
}
return summaryBooks;
}
// 刷新历史回顾列表
export function refreshHistoryList(filter = 'all') {
const listEl = document.getElementById('wechat-history-list');
if (!listEl) return;
const summaryBooks = getSummaryLorebooks(filter);
if (summaryBooks.length === 0) {
const emptyText = filter === 'contact' ? '暂无单聊总结' : filter === 'group' ? '暂无群聊总结' : '暂无总结记录';
listEl.innerHTML = `<div style="text-align: center; color: var(--wechat-text-secondary); padding: 30px;">${emptyText}<br><span style="font-size: 12px;">前往"总结"功能生成总结</span></div>`;
return;
}
listEl.innerHTML = summaryBooks.map(lb => {
// 从名称中提取人名
let displayName = lb.name;
if (lb.name?.startsWith(LOREBOOK_NAME_PREFIX)) {
displayName = lb.name.slice(LOREBOOK_NAME_PREFIX.length, -LOREBOOK_NAME_SUFFIX.length);
}
const isGroup = isGroupSummary(lb);
const entriesCount = lb.entries?.length || 0;
return `
<div class="wechat-history-item" data-index="${lb.originalIndex}" style="padding: 12px; border-bottom: 1px solid var(--wechat-border); cursor: pointer;">
<div style="display: flex; align-items: center; gap: 10px;">
<div style="flex: 1; min-width: 0;">
<div style="font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: #000;">${escapeHtml(displayName)}</div>
<div style="font-size: 12px; color: #000;">${entriesCount} 杯总结 · ${lb.lastUpdated || lb.addedTime || '未知时间'}</div>
</div>
<label class="wechat-toggle wechat-toggle-small" onclick="event.stopPropagation()">
<input type="checkbox" class="wechat-history-toggle" data-index="${lb.originalIndex}" ${lb.enabled !== false ? 'checked' : ''}>
<span class="wechat-toggle-slider"></span>
</label>
</div>
</div>
`;
}).join('');
// 绑定点击事件
listEl.querySelectorAll('.wechat-history-item').forEach(item => {
item.addEventListener('click', () => {
const idx = parseInt(item.dataset.index);
showHistoryDetail(idx);
});
});
// 绑定开关事件
listEl.querySelectorAll('.wechat-history-toggle').forEach(toggle => {
toggle.addEventListener('change', (e) => {
e.stopPropagation();
const idx = parseInt(toggle.dataset.index);
toggleHistoryItem(idx, toggle.checked);
});
});
}
// 切换历史记录项启用状态
export function toggleHistoryItem(index, enabled) {
const settings = getSettings();
if (settings.selectedLorebooks?.[index]) {
settings.selectedLorebooks[index].enabled = enabled;
saveSettingsDebounced();
showToast(enabled ? '已启用' : '已禁用');
}
}
// 显示历史记录详情
export function showHistoryDetail(index) {
const settings = getSettings();
const lorebook = settings.selectedLorebooks?.[index];
if (!lorebook) return;
// 从名称中提取人名
let displayName = lorebook.name;
if (lorebook.name?.startsWith(LOREBOOK_NAME_PREFIX)) {
displayName = lorebook.name.slice(LOREBOOK_NAME_PREFIX.length, -LOREBOOK_NAME_SUFFIX.length);
}
const entries = lorebook.entries || [];
// 创建详情弹窗
const modal = document.createElement('div');
modal.className = 'wechat-modal';
modal.id = 'wechat-history-detail-modal';
modal.innerHTML = `
<div class="wechat-modal-content" style="position: relative; max-width: 400px; max-height: 80vh; overflow-y: auto;">
<button class="wechat-modal-close-x" id="wechat-history-detail-close">×</button>
<div class="wechat-modal-title">${escapeHtml(displayName)}</div>
<div style="font-size: 12px; color: var(--wechat-text-secondary); margin-bottom: 12px;">
${isGroupSummary(lorebook) ? '👥 群聊总结' : '💬 单聊总结'} · ${entries.length}
</div>
<div class="wechat-history-entries" style="max-height: 400px; overflow-y: auto;">
${entries.length === 0 ? '<div style="text-align: center; color: var(--wechat-text-secondary); padding: 20px;">暂无条目</div>' :
entries.map((entry, idx) => `
<div class="wechat-history-entry" data-entry-index="${idx}" style="padding: 12px; border: 1px solid var(--wechat-border); border-radius: 8px; margin-bottom: 10px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
<span style="font-weight: 500;">${escapeHtml(entry.comment || '第' + (idx + 1) + '杯')}</span>
<label class="wechat-toggle wechat-toggle-small">
<input type="checkbox" class="wechat-entry-toggle" data-entry-index="${idx}" ${entry.enabled !== false ? 'checked' : ''}>
<span class="wechat-toggle-slider"></span>
</label>
</div>
<div style="font-size: 12px; color: var(--wechat-text-secondary); margin-bottom: 6px;">
${(entry.keys || []).map(k => `<span style="background: var(--wechat-bg-secondary); padding: 2px 6px; border-radius: 4px; margin-right: 4px;">${escapeHtml(k)}</span>`).join('')}
</div>
<div style="font-size: 13px; line-height: 1.5; color: var(--wechat-text-primary);">${escapeHtml(entry.content || '').substring(0, 200)}${(entry.content?.length || 0) > 200 ? '...' : ''}</div>
</div>
`).join('')
}
</div>
<div class="wechat-modal-actions" style="margin-top: 12px; display: flex; gap: 8px;">
<button class="wechat-btn wechat-btn-primary wechat-btn-small" id="wechat-history-sync" style="flex: 1;">同步到酒馆</button>
<button class="wechat-btn wechat-btn-small" id="wechat-history-refresh" style="flex: 1;">从酒馆刷新</button>
</div>
</div>
`;
document.body.appendChild(modal);
// 关闭按钮
modal.querySelector('#wechat-history-detail-close').addEventListener('click', () => modal.remove());
// 点击背景关闭
modal.addEventListener('click', (e) => {
if (e.target === modal) modal.remove();
});
// 条目开关
modal.querySelectorAll('.wechat-entry-toggle').forEach(toggle => {
toggle.addEventListener('change', (e) => {
const entryIdx = parseInt(toggle.dataset.entryIndex);
if (settings.selectedLorebooks?.[index]?.entries?.[entryIdx]) {
settings.selectedLorebooks[index].entries[entryIdx].enabled = toggle.checked;
saveSettingsDebounced();
}
});
});
// 同步到酒馆按钮
modal.querySelector('#wechat-history-sync').addEventListener('click', async () => {
const btn = modal.querySelector('#wechat-history-sync');
btn.disabled = true;
btn.textContent = '同步中...';
try {
const { syncEntryToSillyTavern } = await import('./summary.js');
for (let i = 0; i < entries.length; i++) {
await syncEntryToSillyTavern(entries[i], i + 1, lorebook.name);
}
showToast('已同步到酒馆');
} catch (err) {
console.error('[可乐] 同步失败:', err);
showToast('同步失败: ' + err.message, '⚠️');
addErrorLog(err, '历史回顾同步');
} finally {
btn.disabled = false;
btn.textContent = '同步到酒馆';
}
});
// 从酒馆刷新按钮
modal.querySelector('#wechat-history-refresh').addEventListener('click', async () => {
const btn = modal.querySelector('#wechat-history-refresh');
btn.disabled = true;
btn.textContent = '刷新中...';
try {
const { refreshLorebookFromTavern } = await import('./favorites.js');
await refreshLorebookFromTavern(lorebook.name, index);
showToast('已从酒馆刷新');
modal.remove();
refreshHistoryList();
} catch (err) {
console.error('[可乐] 从酒馆刷新失败:', err);
showToast('刷新失败: ' + err.message, '⚠️');
addErrorLog(err, '历史回顾刷新');
} finally {
btn.disabled = false;
btn.textContent = '从酒馆刷新';
}
});
}
// 初始化错误捕获(仅捕获插件内部错误)
export function initErrorCapture() {
// 插件错误由各模块调用 addErrorLog 主动记录
// 不再全局捕获 console.error避免记录酒馆其他错误
console.log('[可乐不加冰] 错误日志系统已初始化');
}