/**
* 可乐不加冰 - 主入口(模块化)
*/
console.log('[可乐] main.js 开始加载...');
import { requestSave, setupUnloadSave } from './save-manager.js';
import { loadSettings, getSettings } from './config.js';
import { generatePhoneHTML } from './phone-html.js';
import { showPage, refreshChatList, updateMePageInfo, getUserPersonaFromST, updateTabBadge } from './ui.js';
import { showToast } from './toast.js';
import { ICON_SUCCESS, ICON_INFO } from './icons.js';
import { addContact, refreshContactsList, openContactSettings, saveContactSettings, closeContactSettings, changeContactAvatar, getCurrentEditingContactIndex } from './contacts.js';
import { openChatByContactId, setCurrentChatIndex, sendMessage, showRecalledMessages, currentChatIndex, openChat, updateBlockMenuText, startBlockedAIMessages, stopBlockedAIMessages, showBlockedMessages } from './chat.js';
import { refreshFavoritesList, showLorebookModal, syncCharacterBookToTavern, showAddLorebookPanel, showAddPersonaPanel } from './favorites.js';
import { executeSummary, rollbackSummary, refreshSummaryChatList, selectAllSummaryChats, recoverFromTavernWorldbook } from './summary.js';
import { fetchModelListFromApi } from './ai.js';
import { extractCharacterFromPNG, extractCharacterFromJSON, importCharacterToST } from './character-import.js';
import { setupPhoneAutoCentering, setupPhoneDrag, centerPhoneInViewport } from './phone.js';
import { showGroupCreateModal, closeGroupCreateModal, createGroupChat, sendGroupMessage, isInGroupChat, setCurrentGroupChatIndex, getCurrentGroupIndex, openGroupChat } from './group-chat.js';
import { toggleDarkMode, refreshContextTags } from './settings-ui.js';
import { initFuncPanel, toggleFuncPanel, hideFuncPanel, showExpandVoice, closeExpandPanel, sendExpandContent } from './chat-func-panel.js';
import { initEmojiPanel, toggleEmojiPanel, hideEmojiPanel } from './emoji-panel.js';
import { injectAuthorNote, setupMessageObserver, addExtensionButton } from './st-integration.js';
import { getCurrentTime } from './utils.js';
import { refreshHistoryList, refreshLogsList, clearErrorLogs, initErrorCapture, addErrorLog, renderToyHistory } from './history-logs.js';
import { initChatBackground } from './chat-background.js';
import { initMoments, openMomentsPage, clearContactMoments } from './moments.js';
import { initRedPacketEvents } from './red-packet.js';
import { initTransferEvents } from './transfer.js';
import { initGroupRedPacket } from './group-red-packet.js';
import { initGiftEvents } from './gift.js';
import { initCropper } from './cropper.js';
import { createFloatingBall, showFloatingBall, hideFloatingBall } from './floating-ball.js';
import { testSttApi, testTtsApi } from './voice-api.js';
import { getVoiceRecordingsByContact, deleteVoiceRecording, playVoiceRecording } from './audio-storage.js';
// ========== 历史记录功能 ==========
let currentHistoryTab = 'listen';
let currentHistoryContactIndex = -1;
function openHistoryPage(contactIndex) {
const settings = getSettings();
const contact = settings.contacts?.[contactIndex];
if (!contact) return;
currentHistoryContactIndex = contactIndex;
currentHistoryTab = 'listen';
const page = document.getElementById('wechat-history-page');
if (page) {
page.classList.remove('hidden');
// 重置标签状态
document.querySelectorAll('.wechat-history-tab').forEach(tab => {
tab.classList.toggle('active', tab.dataset.tab === 'listen');
});
renderHistoryContent(contact, 'listen');
}
}
function closeHistoryPage() {
const page = document.getElementById('wechat-history-page');
if (page) {
page.classList.add('hidden');
}
currentHistoryContactIndex = -1;
}
function deleteHistoryRecord(tabType, index) {
const settings = getSettings();
const contact = settings.contacts?.[currentHistoryContactIndex];
if (!contact) return;
if (tabType === 'listen') {
if (contact.listenHistory && contact.listenHistory[index]) {
contact.listenHistory.splice(index, 1);
}
} else if (tabType === 'voice' || tabType === 'video') {
// 从 callHistory 中找到并删除对应类型的记录
const callHistory = contact.callHistory || [];
const typeRecords = callHistory.filter(r => r.type === tabType);
if (typeRecords[index]) {
const originalIndex = callHistory.indexOf(typeRecords[index]);
if (originalIndex >= 0) {
contact.callHistory.splice(originalIndex, 1);
}
}
} else if (tabType === 'toy') {
if (contact.toyHistory && contact.toyHistory[index]) {
contact.toyHistory.splice(index, 1);
}
}
requestSave();
renderHistoryContent(contact, tabType);
}
function switchHistoryTab(tabType) {
currentHistoryTab = tabType;
document.querySelectorAll('.wechat-history-tab').forEach(tab => {
tab.classList.toggle('active', tab.dataset.tab === tabType);
});
const settings = getSettings();
const contact = settings.contacts?.[currentHistoryContactIndex];
if (contact) {
renderHistoryContent(contact, tabType);
}
}
function renderHistoryContent(contact, tabType) {
const contentEl = document.getElementById('wechat-history-content');
if (!contentEl) return;
// 心动瞬间使用专门的渲染函数
if (tabType === 'toy') {
renderToyHistory(contact);
// 绑定心动瞬间的删除按钮事件
contentEl.querySelectorAll('.wechat-history-delete-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
const index = parseInt(btn.dataset.index);
deleteHistoryRecord('toy', index);
});
});
// 绑定标签内的叉叉按钮事件
contentEl.querySelectorAll('.wechat-toy-target-close-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
const index = parseInt(btn.dataset.index);
deleteHistoryRecord('toy', index);
});
});
return;
}
// 语音回放使用专门的渲染函数
if (tabType === 'playback') {
renderVoicePlaybackContent(contact);
return;
}
const context = window.SillyTavern?.getContext?.() || {};
const userName = context.name1 || '用户';
let records = [];
if (tabType === 'listen') {
records = contact.listenHistory || [];
} else {
// 从 callHistory 中筛选 voice 或 video
const callHistory = contact.callHistory || [];
records = callHistory.filter(r => r.type === tabType);
}
if (records.length === 0) {
const emptyText = tabType === 'listen' ? '暂无一起听记录' :
tabType === 'voice' ? '暂无语音通话记录' : '暂无视频通话记录';
contentEl.innerHTML = `
`;
return;
}
// 按时间倒序排列
const sortedRecords = [...records].sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0));
let html = '';
for (let i = 0; i < sortedRecords.length; i++) {
const record = sortedRecords[i];
const time = record.time || '未知时间';
const duration = record.duration || '';
const messages = record.messages || [];
const originalIndex = records.indexOf(record);
html += ``;
html += ``;
// 一起听显示歌曲信息
if (tabType === 'listen' && record.song) {
const songName = record.song.name || '未知歌曲';
const songArtist = record.song.artist || '未知歌手';
html += `
[${escapeHtml(songName)} - ${escapeHtml(songArtist)}]
`;
}
// 消息列表
if (messages.length > 0) {
html += `
`;
for (const msg of messages) {
const isUser = msg.role === 'user';
const senderName = isUser ? userName : contact.name;
const senderClass = isUser ? 'user' : '';
html += `
`;
html += `${escapeHtml(senderName)}: `;
html += `${escapeHtml(msg.content || '')}`;
html += `
`;
}
html += `
`;
}
html += `
`;
}
contentEl.innerHTML = html;
// 绑定删除按钮事件
contentEl.querySelectorAll('.wechat-history-delete-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
const tab = btn.dataset.tab;
const index = parseInt(btn.dataset.index);
deleteHistoryRecord(tab, index);
});
});
}
// 渲染语音回放内容
async function renderVoicePlaybackContent(contact) {
const contentEl = document.getElementById('wechat-history-content');
if (!contentEl) return;
const contactIndex = currentHistoryContactIndex;
if (contactIndex < 0) {
contentEl.innerHTML = '';
return;
}
// 显示加载状态
contentEl.innerHTML = '';
try {
const recordings = await getVoiceRecordingsByContact(contactIndex);
if (!recordings || recordings.length === 0) {
contentEl.innerHTML = `
暂无语音回放记录
实时语音通话结束后可选择保存语音
`;
return;
}
// 按保存时间倒序排列
const sortedRecordings = [...recordings].sort((a, b) => (b.savedAt || 0) - (a.savedAt || 0));
let html = '';
for (const recording of sortedRecordings) {
const savedTime = recording.savedAt ? new Date(recording.savedAt).toLocaleString('zh-CN', {
month: 'numeric',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
}) : '未知时间';
const durationSec = Math.round(recording.duration || 0);
const durationStr = durationSec > 0 ? `${durationSec}"` : '?秒';
html += `
${escapeHtml(recording.text || '')}
`;
}
html += '
';
contentEl.innerHTML = html;
// 绑定播放按钮事件
contentEl.querySelectorAll('.wechat-voice-playback-btn').forEach(btn => {
btn.addEventListener('click', async (e) => {
e.stopPropagation();
const id = parseInt(btn.dataset.id);
try {
btn.disabled = true;
btn.innerHTML = '';
await playVoiceRecording(id);
} catch (err) {
console.error('[可乐] 播放语音失败:', err);
showToast('播放失败', '⚠️');
} finally {
btn.disabled = false;
btn.innerHTML = '';
}
});
});
// 绑定删除按钮事件
contentEl.querySelectorAll('.wechat-voice-playback-delete').forEach(btn => {
btn.addEventListener('click', async (e) => {
e.stopPropagation();
const id = parseInt(btn.dataset.id);
if (confirm('确定要删除这条语音吗?')) {
try {
await deleteVoiceRecording(id);
showToast('已删除', '✓');
// 重新渲染
renderVoicePlaybackContent(contact);
} catch (err) {
console.error('[可乐] 删除语音失败:', err);
showToast('删除失败', '⚠️');
}
}
});
});
} catch (err) {
console.error('[可乐] 加载语音记录失败:', err);
contentEl.innerHTML = `
⚠️
加载失败
${escapeHtml(err.message || '')}
`;
}
}
function escapeHtml(text) {
if (!text) return '';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function initHistoryEvents() {
// 返回按钮
document.getElementById('wechat-history-back-btn')?.addEventListener('click', closeHistoryPage);
// 标签切换
document.querySelectorAll('.wechat-history-tab').forEach(tab => {
tab.addEventListener('click', () => {
const tabType = tab.dataset.tab;
if (tabType) {
switchHistoryTab(tabType);
}
});
});
}
// ========== 历史记录功能结束 ==========
function normalizeModelListForSelect(models) {
return (models || []).map(m => {
if (typeof m === 'string') return { id: m, name: m };
return { id: m?.id || '', name: m?.name || m?.id || '' };
}).filter(m => m.id);
}
function restoreModelSelect() {
// select 元素在 HTML 生成时已经包含了选项,无需额外恢复
}
function restoreGroupModelSelect() {
// select 元素在 HTML 生成时已经包含了选项,无需额外恢复
}
function seedDefaultUserPersonaFromST(settings) {
if (Array.isArray(settings.userPersonas) && settings.userPersonas.length > 0) return false;
const stPersona = getUserPersonaFromST();
const content = stPersona?.description?.trim();
if (!content) return false;
const now = new Date();
const timeStr = `${now.getFullYear()}-${(now.getMonth() + 1).toString().padStart(2, '0')}-${now.getDate().toString().padStart(2, '0')} ${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`;
settings.userPersonas = [{
name: (stPersona?.name || '').trim() || '用户设定',
content,
enabled: true,
addedTime: timeStr,
}];
return true;
}
async function refreshModelSelect() {
const select = document.getElementById('wechat-model-select');
const refreshBtn = document.getElementById('wechat-refresh-models');
if (!select) return;
const settings = getSettings();
const apiUrl = document.getElementById('wechat-api-url')?.value?.trim() || settings.apiUrl || '';
const apiKey = document.getElementById('wechat-api-key')?.value?.trim() || settings.apiKey || '';
if (!apiUrl) {
showToast('请先填写 API 地址', 'info');
return;
}
const originalText = refreshBtn?.textContent;
if (refreshBtn) {
refreshBtn.textContent = '加载中...';
refreshBtn.disabled = true;
}
try {
const modelIds = await fetchModelListFromApi(apiUrl, apiKey);
// 更新 select 选项
select.innerHTML = '' +
modelIds.map(id => ``).join('');
settings.modelList = modelIds;
requestSave();
showToast(`获取到 ${modelIds.length} 个模型`);
} catch (err) {
console.error('[可乐] 获取模型列表失败:', err);
showToast(`获取失败,请手动输入模型名`, '⚠️');
} finally {
if (refreshBtn) {
refreshBtn.textContent = originalText;
refreshBtn.disabled = false;
}
}
}
function syncContextEnabledUI(enabled) {
const display = document.getElementById('wechat-context-level-display');
if (display) display.textContent = enabled ? '已开启' : '已关闭';
const settingsSection = document.getElementById('wechat-context-settings');
if (settingsSection) {
settingsSection.style.opacity = enabled ? '1' : '0.5';
settingsSection.style.pointerEvents = enabled ? 'auto' : 'none';
}
}
function updateWalletAmountDisplay() {
const settings = getSettings();
const amountEl = document.getElementById('wechat-wallet-amount');
if (!amountEl) return;
const amount = settings.walletAmount || '5773.89';
amountEl.textContent = amount.startsWith('¥') ? amount : `¥${amount}`;
}
// ===== 缩小/恢复手机功能 =====
let minimizeState = {
isDragging: false,
startX: 0,
startY: 0,
initialLeft: 0,
initialTop: 0,
hasMoved: false
};
// 悬浮窗开关
function toggleFloatingBallEnabled() {
const settings = getSettings();
const isEnabled = settings.floatingBallEnabled !== false;
if (isEnabled) {
// 关闭悬浮窗
settings.floatingBallEnabled = false;
hideFloatingBall();
updateFloatingBallMenuText(false);
} else {
// 开启悬浮窗
settings.floatingBallEnabled = true;
// 只有非缩小状态才显示
const phone = document.getElementById('wechat-phone');
if (!phone?.classList.contains('minimized')) {
showFloatingBall();
}
updateFloatingBallMenuText(true);
}
requestSave();
}
function updateFloatingBallMenuText(enabled) {
const textEl = document.getElementById('wechat-floating-ball-text');
if (textEl) {
textEl.textContent = enabled ? '关闭悬浮窗' : '开启悬浮窗';
}
}
function setupPhoneMinimize() {
const phone = document.getElementById('wechat-phone');
const minimizeBtn = document.getElementById('wechat-minimize-btn');
if (!phone || !minimizeBtn) return;
// 点击右上角图标 - 缩小 (PC)
minimizeBtn.addEventListener('click', (e) => {
e.stopPropagation();
e.preventDefault();
minimizePhone();
});
// 移动端触摸支持
let minimizeBtnTouchMoved = false;
minimizeBtn.addEventListener('touchstart', (e) => {
minimizeBtnTouchMoved = false;
}, { passive: true });
minimizeBtn.addEventListener('touchmove', (e) => {
minimizeBtnTouchMoved = true;
}, { passive: true });
minimizeBtn.addEventListener('touchend', (e) => {
if (!minimizeBtnTouchMoved) {
e.stopPropagation();
e.preventDefault();
minimizePhone();
}
});
// 缩小后点击恢复 + 拖动支持
phone.addEventListener('mousedown', onMinimizedDragStart);
document.addEventListener('mousemove', onMinimizedDragMove);
document.addEventListener('mouseup', onMinimizedDragEnd);
// 触摸支持
phone.addEventListener('touchstart', onMinimizedDragStart, { passive: false });
document.addEventListener('touchmove', onMinimizedDragMove, { passive: false });
document.addEventListener('touchend', onMinimizedDragEnd);
}
function minimizePhone() {
const phone = document.getElementById('wechat-phone');
if (!phone) return;
// 获取当前位置
const rect = phone.getBoundingClientRect();
const settings = getSettings();
// 保存原始位置
if (!settings.phoneOriginalPosition) {
settings.phoneOriginalPosition = {
left: phone.style.left || rect.left + 'px',
top: phone.style.top || rect.top + 'px'
};
}
// 缩小后移到右下角
const scale = 0.25;
const phoneWidth = rect.width * scale;
const phoneHeight = rect.height * scale;
// 使用保存的缩小位置或默认右下角
const savedMinPos = settings.phoneMinimizedPosition;
let targetLeft, targetTop;
if (savedMinPos) {
targetLeft = savedMinPos.left;
targetTop = savedMinPos.top;
} else {
targetLeft = window.innerWidth - phoneWidth - 20;
targetTop = window.innerHeight - phoneHeight - 20;
}
phone.style.left = targetLeft + 'px';
phone.style.top = targetTop + 'px';
phone.style.right = 'auto';
phone.style.bottom = 'auto';
phone.classList.add('minimized');
// 缩小时隐藏悬浮球
hideFloatingBall();
requestSave();
}
function restorePhone() {
const phone = document.getElementById('wechat-phone');
if (!phone) return;
const settings = getSettings();
phone.classList.remove('minimized');
// 恢复原始位置或居中
if (settings.phoneOriginalPosition) {
phone.style.left = settings.phoneOriginalPosition.left;
phone.style.top = settings.phoneOriginalPosition.top;
}
// 恢复时根据设置显示悬浮球
if (settings.floatingBallEnabled !== false) {
showFloatingBall();
}
requestSave();
}
function onMinimizedDragStart(e) {
const phone = document.getElementById('wechat-phone');
if (!phone || !phone.classList.contains('minimized')) return;
minimizeState.isDragging = true;
minimizeState.hasMoved = false;
const rect = phone.getBoundingClientRect();
// 缩小状态下需要考虑缩放后的实际位置
minimizeState.initialLeft = parseFloat(phone.style.left) || rect.left;
minimizeState.initialTop = parseFloat(phone.style.top) || rect.top;
if (e.type === 'touchstart') {
minimizeState.startX = e.touches[0].clientX;
minimizeState.startY = e.touches[0].clientY;
e.preventDefault();
} else {
minimizeState.startX = e.clientX;
minimizeState.startY = e.clientY;
}
phone.style.transition = 'none';
}
function onMinimizedDragMove(e) {
if (!minimizeState.isDragging) return;
const phone = document.getElementById('wechat-phone');
if (!phone || !phone.classList.contains('minimized')) return;
let clientX, clientY;
if (e.type === 'touchmove') {
clientX = e.touches[0].clientX;
clientY = e.touches[0].clientY;
e.preventDefault();
} else {
clientX = e.clientX;
clientY = e.clientY;
}
const deltaX = clientX - minimizeState.startX;
const deltaY = clientY - minimizeState.startY;
if (Math.abs(deltaX) > 5 || Math.abs(deltaY) > 5) {
minimizeState.hasMoved = true;
}
// 直接使用当前触摸/鼠标位置减去元素尺寸的一半,让元素中心跟随手指
const scale = 0.25;
const rect = phone.getBoundingClientRect();
const scaledWidth = rect.width;
const scaledHeight = rect.height;
// 计算新位置:让缩小后的手机中心跟随手指
let newLeft = clientX - scaledWidth / 2;
let newTop = clientY - scaledHeight / 2;
// 限制在视口内
const maxX = window.innerWidth - scaledWidth;
const maxY = window.innerHeight - scaledHeight;
newLeft = Math.min(Math.max(0, newLeft), maxX);
newTop = Math.min(Math.max(0, newTop), maxY);
phone.style.left = newLeft + 'px';
phone.style.top = newTop + 'px';
}
function onMinimizedDragEnd(e) {
if (!minimizeState.isDragging) return;
const phone = document.getElementById('wechat-phone');
minimizeState.isDragging = false;
if (phone) {
phone.style.transition = '';
}
if (!minimizeState.hasMoved) {
// 没有移动,视为点击 - 恢复
restorePhone();
} else {
// 移动了,保存位置
if (phone && phone.classList.contains('minimized')) {
const settings = getSettings();
settings.phoneMinimizedPosition = {
left: parseFloat(phone.style.left),
top: parseFloat(phone.style.top)
};
requestSave();
}
}
}
function bindEvents() {
// ===== 缩小/恢复手机功能 =====
setupPhoneMinimize();
// 添加按钮 - 显示下拉菜单
document.getElementById('wechat-add-btn')?.addEventListener('click', (e) => {
e.stopPropagation();
document.getElementById('wechat-dropdown-menu')?.classList.toggle('hidden');
});
// 点击其他地方关闭下拉菜单
document.getElementById('wechat-phone')?.addEventListener('click', (e) => {
if (!e.target.closest('#wechat-add-btn') && !e.target.closest('#wechat-dropdown-menu')) {
document.getElementById('wechat-dropdown-menu')?.classList.add('hidden');
}
});
// 通讯录页面的添加按钮 - 直接进入添加朋友页面
document.getElementById('wechat-contacts-add-btn')?.addEventListener('click', () => {
showPage('wechat-add-page');
});
// 下拉菜单 - 添加朋友
document.getElementById('wechat-menu-add-friend')?.addEventListener('click', () => {
document.getElementById('wechat-dropdown-menu')?.classList.add('hidden');
showPage('wechat-add-page');
});
// 下拉菜单 - 发起群聊
document.getElementById('wechat-menu-group')?.addEventListener('click', () => {
document.getElementById('wechat-dropdown-menu')?.classList.add('hidden');
showGroupCreateModal();
});
// 下拉菜单 - 其他选项(暂时只关闭菜单)
['wechat-menu-scan', 'wechat-menu-pay'].forEach(id => {
document.getElementById(id)?.addEventListener('click', () => {
document.getElementById('wechat-dropdown-menu')?.classList.add('hidden');
});
});
// 下拉菜单 - 悬浮窗开关
document.getElementById('wechat-menu-floating-ball')?.addEventListener('click', () => {
document.getElementById('wechat-dropdown-menu')?.classList.add('hidden');
toggleFloatingBallEnabled();
});
// ===== 群聊创建弹窗事件 =====
document.getElementById('wechat-group-create-close')?.addEventListener('click', closeGroupCreateModal);
document.getElementById('wechat-group-create-confirm')?.addEventListener('click', createGroupChat);
// 返回按钮
document.getElementById('wechat-back-btn')?.addEventListener('click', () => {
showPage('wechat-main-content');
});
document.getElementById('wechat-chat-back-btn')?.addEventListener('click', () => {
setCurrentChatIndex(-1);
setCurrentGroupChatIndex(-1);
// 清除群聊标记
const messagesContainer = document.getElementById('wechat-chat-messages');
if (messagesContainer) {
messagesContainer.dataset.isGroup = 'false';
messagesContainer.dataset.groupIndex = '-1';
// 清除背景
messagesContainer.style.backgroundImage = '';
}
// 关闭所有聊天页面板
document.getElementById('wechat-chat-menu')?.classList.add('hidden');
document.getElementById('wechat-recalled-panel')?.classList.add('hidden');
document.getElementById('wechat-chat-bg-panel')?.classList.add('hidden');
showPage('wechat-main-content');
refreshContactsList();
refreshChatList();
});
// ===== 聊天页菜单事件 =====
// 三个点按钮 - 显示聊天菜单
document.getElementById('wechat-chat-more-btn')?.addEventListener('click', (e) => {
e.stopPropagation();
const menu = document.getElementById('wechat-chat-menu');
const recalledPanel = document.getElementById('wechat-recalled-panel');
const bgPanel = document.getElementById('wechat-chat-bg-panel');
recalledPanel?.classList.add('hidden');
bgPanel?.classList.add('hidden');
menu?.classList.toggle('hidden');
});
// 撤回消息菜单项 - 显示撤回消息区
document.getElementById('wechat-menu-recalled')?.addEventListener('click', () => {
document.getElementById('wechat-chat-menu')?.classList.add('hidden');
showRecalledMessages();
});
// 关闭撤回消息区面板
document.getElementById('wechat-recalled-close')?.addEventListener('click', () => {
document.getElementById('wechat-recalled-panel')?.classList.add('hidden');
});
// 查看TA的朋友圈
document.getElementById('wechat-menu-moments')?.addEventListener('click', () => {
document.getElementById('wechat-chat-menu')?.classList.add('hidden');
if (currentChatIndex >= 0) {
openMomentsPage(currentChatIndex);
}
});
// 查看历史记录
document.getElementById('wechat-menu-history')?.addEventListener('click', () => {
document.getElementById('wechat-chat-menu')?.classList.add('hidden');
if (currentChatIndex >= 0) {
openHistoryPage(currentChatIndex);
}
});
// 清空TA的朋友圈
document.getElementById('wechat-menu-clear-moments')?.addEventListener('click', () => {
document.getElementById('wechat-chat-menu')?.classList.add('hidden');
if (currentChatIndex >= 0) {
clearContactMoments(currentChatIndex);
}
});
// 清空当前聊天(支持单聊和群聊)
document.getElementById('wechat-menu-clear-chat')?.addEventListener('click', () => {
document.getElementById('wechat-chat-menu')?.classList.add('hidden');
const groupIndex = getCurrentGroupIndex();
const settings = getSettings();
// 群聊清空
if (groupIndex >= 0) {
if (!confirm('确定要清空当前群聊记录吗?此操作不可恢复。')) return;
const groupChat = settings.groupChats?.[groupIndex];
if (groupChat) {
groupChat.chatHistory = [];
groupChat.lastMessage = '';
requestSave();
openGroupChat(groupIndex); // 刷新群聊界面
showToast('群聊记录已清空');
}
return;
}
// 单聊清空
if (currentChatIndex < 0) return;
if (!confirm('确定要清空当前聊天记录吗?此操作不可恢复。')) return;
const contact = settings.contacts[currentChatIndex];
if (contact) {
contact.chatHistory = [];
contact.lastMessage = '';
requestSave();
openChat(currentChatIndex); // 刷新聊天界面
showToast('聊天记录已清空');
}
});
// 拉黑/取消拉黑功能
document.getElementById('wechat-menu-block')?.addEventListener('click', async () => {
document.getElementById('wechat-chat-menu')?.classList.add('hidden');
// 群聊不支持拉黑
const groupIndex = getCurrentGroupIndex();
if (groupIndex >= 0) {
showToast('群聊暂不支持此功能', 'info');
return;
}
if (currentChatIndex < 0) return;
const settings = getSettings();
const contact = settings.contacts[currentChatIndex];
if (!contact) return;
const isBlocked = contact.isBlocked === true;
if (isBlocked) {
// 取消拉黑
if (!confirm(`确定要取消拉黑"${contact.name}"吗?`)) return;
contact.isBlocked = false;
stopBlockedAIMessages(contact);
requestSave();
refreshChatList();
updateBlockMenuText(false);
showToast('已取消拉黑', '✓');
// 显示被拉黑期间AI发送的消息
await showBlockedMessages(contact);
} else {
// 拉黑
if (!confirm(`确定要拉黑"${contact.name}"吗?拉黑后对方将无法给你发消息。`)) return;
contact.isBlocked = true;
requestSave();
refreshChatList();
updateBlockMenuText(true);
showToast('已拉黑', '🚫');
// 开始AI被拉黑期间发消息
startBlockedAIMessages(contact);
}
});
// 点击聊天页其他地方关闭菜单和面板
document.getElementById('wechat-chat-page')?.addEventListener('click', (e) => {
if (!e.target.closest('#wechat-chat-more-btn') && !e.target.closest('#wechat-chat-menu')) {
document.getElementById('wechat-chat-menu')?.classList.add('hidden');
}
if (!e.target.closest('#wechat-chat-bg-panel') && !e.target.closest('#wechat-chat-menu')) {
document.getElementById('wechat-chat-bg-panel')?.classList.add('hidden');
}
});
document.getElementById('wechat-settings-back-btn')?.addEventListener('click', () => {
showPage('wechat-me-page');
});
document.getElementById('wechat-favorites-back-btn')?.addEventListener('click', () => {
showPage('wechat-me-page');
});
// 导入 PNG/JSON
document.getElementById('wechat-import-png')?.addEventListener('click', () => {
document.getElementById('wechat-file-png')?.click();
});
document.getElementById('wechat-import-json')?.addEventListener('click', () => {
document.getElementById('wechat-file-json')?.click();
});
// PNG 文件选择
document.getElementById('wechat-file-png')?.addEventListener('change', async function (e) {
const file = e.target.files[0];
if (!file) return;
try {
const charData = await extractCharacterFromPNG(file);
charData.file = file;
if (addContact(charData)) {
showToast('导入成功');
try {
await importCharacterToST(charData);
} catch (err) {
console.log('导入到酒馆失败(可忽略):', err.message);
}
// 同步角色卡内置世界书
const lorebookName = await syncCharacterBookToTavern(charData);
if (lorebookName) {
showToast(`角色书「${lorebookName}」已同步`);
}
showPage('wechat-main-content');
}
} catch (err) {
showToast(err.message, '⚠️');
}
this.value = '';
});
// JSON 文件选择
document.getElementById('wechat-file-json')?.addEventListener('change', async function (e) {
const file = e.target.files[0];
if (!file) return;
try {
const charData = await extractCharacterFromJSON(file);
charData.file = file;
if (addContact(charData)) {
showToast('导入成功');
try {
await importCharacterToST(charData);
} catch (err) {
console.log('导入到酒馆失败(可忽略):', err.message);
}
// 同步角色卡内置世界书
const lorebookName = await syncCharacterBookToTavern(charData);
if (lorebookName) {
showToast(`角色书「${lorebookName}」已同步`);
}
showPage('wechat-main-content');
}
} catch (err) {
showToast(err.message, '⚠️');
}
this.value = '';
});
// 深色模式切换
document.getElementById('wechat-dark-toggle')?.addEventListener('click', toggleDarkMode);
// 自动注入提示
document.getElementById('wechat-auto-inject-toggle')?.addEventListener('click', () => {
const settings = getSettings();
settings.autoInjectPrompt = !settings.autoInjectPrompt;
document.getElementById('wechat-auto-inject-toggle')?.classList.toggle('on', settings.autoInjectPrompt);
// 展开/收起编辑区域
const contentDiv = document.getElementById('wechat-auto-inject-content');
if (contentDiv) {
contentDiv.classList.toggle('hidden', !settings.autoInjectPrompt);
}
requestSave();
if (settings.autoInjectPrompt) injectAuthorNote();
});
// 保存作者注释模板
document.getElementById('wechat-save-author-note')?.addEventListener('click', () => {
const settings = getSettings();
settings.authorNoteCustom = document.getElementById('wechat-author-note-content')?.value || '';
requestSave();
showToast('作者注释模板已保存');
});
// 哈基米破限开关
document.getElementById('wechat-hakimi-toggle')?.addEventListener('click', () => {
const settings = getSettings();
settings.hakimiBreakLimit = !settings.hakimiBreakLimit;
document.getElementById('wechat-hakimi-toggle')?.classList.toggle('on', settings.hakimiBreakLimit);
// 展开/收起编辑区域
const contentDiv = document.getElementById('wechat-hakimi-content');
if (contentDiv) {
contentDiv.classList.toggle('hidden', !settings.hakimiBreakLimit);
}
requestSave();
showToast(settings.hakimiBreakLimit ? '哈基米破限已开启' : '哈基米破限已关闭');
});
// 保存哈基米破限词
document.getElementById('wechat-save-hakimi')?.addEventListener('click', () => {
const settings = getSettings();
settings.hakimiCustomPrompt = document.getElementById('wechat-hakimi-prompt')?.value || '';
requestSave();
showToast('破限提示词已保存');
});
// ===== Meme表情包事件 =====
// 关闭面板
document.getElementById('wechat-meme-stickers-close')?.addEventListener('click', () => {
document.getElementById('wechat-meme-stickers-panel')?.classList.add('hidden');
});
// Meme开关
document.getElementById('wechat-meme-stickers-toggle')?.addEventListener('click', () => {
const settings = getSettings();
settings.memeStickersEnabled = !settings.memeStickersEnabled;
const toggle = document.getElementById('wechat-meme-stickers-toggle');
toggle?.classList.toggle('on', settings.memeStickersEnabled);
requestSave();
showToast(settings.memeStickersEnabled ? 'Meme表情包已启用' : 'Meme表情包已禁用');
});
// 添加表情包 - 弹出文本输入框
document.getElementById('wechat-add-meme-sticker')?.addEventListener('click', () => {
// 创建弹窗
const modal = document.createElement('div');
modal.className = 'wechat-modal';
modal.id = 'wechat-add-meme-modal';
modal.innerHTML = `
添加表情包
输入猫箱格式的文件名,每行一个
格式:名称+6位ID.扩展名
例如:是的主人yvrgdc.jpg
`;
const phoneContainer = document.getElementById('wechat-phone');
if (phoneContainer) {
phoneContainer.appendChild(modal);
} else {
document.body.appendChild(modal);
}
// 聚焦输入框
document.getElementById('wechat-meme-input')?.focus();
// 取消按钮
document.getElementById('wechat-meme-cancel')?.addEventListener('click', () => modal.remove());
// 点击背景关闭
modal.addEventListener('click', (e) => {
if (e.target === modal) modal.remove();
});
// 确认添加
document.getElementById('wechat-meme-confirm')?.addEventListener('click', () => {
const input = document.getElementById('wechat-meme-input');
const text = input?.value?.trim();
if (!text) {
modal.remove();
return;
}
// 解析输入的每一行
const lines = text.split('\n').map(s => s.trim()).filter(s => s);
if (lines.length === 0) {
modal.remove();
return;
}
// 添加到表情包列表
const textarea = document.getElementById('wechat-meme-stickers-list');
if (textarea) {
const currentList = textarea.value.trim();
const updatedList = currentList ? currentList + '\n' + lines.join('\n') : lines.join('\n');
textarea.value = updatedList;
showToast(`已添加 ${lines.length} 个表情包`);
}
modal.remove();
});
});
// ===== 角色设置弹窗事件 =====
// 关闭按钮
document.getElementById('wechat-contact-settings-close')?.addEventListener('click', closeContactSettings);
// 保存按钮
document.getElementById('wechat-contact-settings-save')?.addEventListener('click', saveContactSettings);
// 更换头像按钮
document.getElementById('wechat-change-avatar-btn')?.addEventListener('click', () => {
const index = getCurrentEditingContactIndex();
if (index >= 0) changeContactAvatar(index);
});
// 独立API开关
document.getElementById('wechat-contact-custom-api-toggle')?.addEventListener('click', () => {
const toggle = document.getElementById('wechat-contact-custom-api-toggle');
const apiSettingsDiv = document.getElementById('wechat-contact-api-settings');
toggle?.classList.toggle('on');
const isOn = toggle?.classList.contains('on');
if (apiSettingsDiv) {
if (isOn) {
apiSettingsDiv.classList.remove('hidden');
apiSettingsDiv.style.display = 'flex';
} else {
apiSettingsDiv.classList.add('hidden');
apiSettingsDiv.style.display = 'none';
}
}
});
// 角色独立哈基米开关
document.getElementById('wechat-contact-hakimi-toggle')?.addEventListener('click', () => {
document.getElementById('wechat-contact-hakimi-toggle')?.classList.toggle('on');
});
// 角色独立API获取模型按钮
document.getElementById('wechat-contact-fetch-model')?.addEventListener('click', async () => {
const apiUrl = document.getElementById('wechat-contact-api-url')?.value?.trim();
const apiKey = document.getElementById('wechat-contact-api-key')?.value?.trim();
const modelSelect = document.getElementById('wechat-contact-model-select');
const fetchBtn = document.getElementById('wechat-contact-fetch-model');
if (!apiUrl) {
showToast('请先填写API地址', 'info');
return;
}
fetchBtn.textContent = '...';
fetchBtn.disabled = true;
try {
const { fetchModelListFromApi } = await import('./ai.js');
const models = await fetchModelListFromApi(apiUrl, apiKey);
if (models.length > 0) {
const currentValue = modelSelect?.value || '';
modelSelect.innerHTML = '' +
models.map(m => ``).join('');
showToast(`获取到 ${models.length} 个模型`);
} else {
showToast('未找到可用模型', 'info');
}
} catch (err) {
console.error('[可乐] 获取模型失败:', err);
showToast('获取失败: ' + err.message, '⚠️');
} finally {
fetchBtn.textContent = '获取';
fetchBtn.disabled = false;
}
});
// 角色独立API手动输入按钮
document.getElementById('wechat-contact-model-manual')?.addEventListener('click', () => {
const selectWrapper = document.getElementById('wechat-contact-model-select-wrapper');
const inputWrapper = document.getElementById('wechat-contact-model-input-wrapper');
const modelSelect = document.getElementById('wechat-contact-model-select');
const modelInput = document.getElementById('wechat-contact-model-input');
// 将当前选中的值复制到输入框
if (modelSelect?.value) {
modelInput.value = modelSelect.value;
}
selectWrapper.style.display = 'none';
inputWrapper.style.display = 'flex';
modelInput?.focus();
});
// 角色独立API返回按钮
document.getElementById('wechat-contact-model-back')?.addEventListener('click', () => {
const selectWrapper = document.getElementById('wechat-contact-model-select-wrapper');
const inputWrapper = document.getElementById('wechat-contact-model-input-wrapper');
const modelSelect = document.getElementById('wechat-contact-model-select');
const modelInput = document.getElementById('wechat-contact-model-input');
// 如果输入框有值,尝试在下拉列表中选中,或添加为新选项
const inputValue = modelInput?.value?.trim();
if (inputValue && modelSelect) {
const existingOption = Array.from(modelSelect.options).find(opt => opt.value === inputValue);
if (existingOption) {
modelSelect.value = inputValue;
} else {
// 添加为新选项并选中
const newOption = document.createElement('option');
newOption.value = inputValue;
newOption.textContent = inputValue;
modelSelect.appendChild(newOption);
modelSelect.value = inputValue;
}
}
selectWrapper.style.display = 'flex';
inputWrapper.style.display = 'none';
});
// 角色独立API测试连接按钮
document.getElementById('wechat-contact-test-api')?.addEventListener('click', async () => {
const apiUrl = document.getElementById('wechat-contact-api-url')?.value?.trim();
const apiKey = document.getElementById('wechat-contact-api-key')?.value?.trim();
// 优先从输入框获取,其次从下拉列表获取
const inputWrapper = document.getElementById('wechat-contact-model-input-wrapper');
const isManualMode = inputWrapper?.style.display === 'flex';
const model = isManualMode
? document.getElementById('wechat-contact-model-input')?.value?.trim()
: document.getElementById('wechat-contact-model-select')?.value?.trim();
const testBtn = document.getElementById('wechat-contact-test-api');
if (!apiUrl) {
showToast('请先填写API地址', 'info');
return;
}
if (!model) {
showToast('请先填写或选择模型', 'info');
return;
}
testBtn.textContent = '测试中...';
testBtn.disabled = true;
try {
const chatUrl = apiUrl.replace(/\/+$/, '') + '/chat/completions';
const headers = { 'Content-Type': 'application/json' };
if (apiKey) {
headers['Authorization'] = `Bearer ${apiKey}`;
}
const response = await fetch(chatUrl, {
method: 'POST',
headers,
body: JSON.stringify({
model: model,
messages: [{ role: 'user', content: '请回复"连接成功"' }],
max_tokens: 8196
})
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`${response.status}: ${errorText.substring(0, 100)}`);
}
const data = await response.json();
const reply = data.choices?.[0]?.message?.content || '';
showToast(`连接成功!回复: ${reply.substring(0, 20)}...`, 'success');
} catch (err) {
console.error('[可乐] 测试连接失败:', err);
showToast('❌ 连接失败: ' + err.message, '⚠️');
} finally {
testBtn.textContent = '测试连接';
testBtn.disabled = false;
}
});
// ===== 群聊设置事件 =====
// 群聊提示词注入开关
document.getElementById('wechat-group-inject-toggle')?.addEventListener('click', () => {
const settings = getSettings();
settings.groupAutoInjectPrompt = !settings.groupAutoInjectPrompt;
document.getElementById('wechat-group-inject-toggle')?.classList.toggle('on', settings.groupAutoInjectPrompt);
// 展开/收起编辑区域
const contentDiv = document.getElementById('wechat-group-inject-content');
if (contentDiv) {
contentDiv.classList.toggle('hidden', !settings.groupAutoInjectPrompt);
}
requestSave();
showToast(settings.groupAutoInjectPrompt ? '群聊提示词注入已开启' : '群聊提示词注入已关闭');
});
// 保存群聊作者注释
document.getElementById('wechat-save-group-note')?.addEventListener('click', () => {
const settings = getSettings();
settings.userGroupAuthorNote = document.getElementById('wechat-group-author-note')?.value || '';
requestSave();
showToast('群聊作者注释已保存');
});
// 聊天输入框发送消息(支持单聊和群聊)
const chatInput = document.getElementById('wechat-input');
// 更新发送按钮状态(全局可用)
window.updateSendButtonState = () => {
const moreBtn = document.querySelector('.wechat-chat-input-more');
const sendText = moreBtn?.querySelector('.wechat-input-send-text');
const moreIcon = moreBtn?.querySelector('.wechat-input-more-icon');
if (!sendText || !moreIcon) return;
const input = document.getElementById('wechat-input');
const hasText = input?.value?.trim();
if (hasText) {
sendText.style.display = 'inline-block';
moreIcon.style.display = 'none';
} else {
sendText.style.display = 'none';
moreIcon.style.display = 'inline-block';
}
};
// 监听输入变化
chatInput?.addEventListener('input', window.updateSendButtonState);
// 监听聚焦时也更新状态
chatInput?.addEventListener('focus', window.updateSendButtonState);
chatInput?.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
const text = chatInput.value?.trim();
if (!text) return;
// 调试日志
const messagesContainer = document.getElementById('wechat-chat-messages');
console.log('[可乐] Enter 键发送消息:', {
text: text.substring(0, 20),
isGroup: messagesContainer?.dataset?.isGroup,
groupIndex: messagesContainer?.dataset?.groupIndex,
isInGroupChatResult: isInGroupChat()
});
if (isInGroupChat()) {
console.log('[可乐] 调用 sendGroupMessage');
sendGroupMessage(text);
} else {
console.log('[可乐] 调用 sendMessage (单聊)');
sendMessage(text);
}
// 发送后更新按钮状态
setTimeout(window.updateSendButtonState, 50);
}
});
// 聊天输入区按钮
document.querySelector('.wechat-chat-input-more')?.addEventListener('click', () => {
const text = chatInput?.value?.trim();
if (text) {
// 有文字时发送消息
if (isInGroupChat()) {
sendGroupMessage(text);
} else {
sendMessage(text);
}
// 发送后更新按钮状态
setTimeout(window.updateSendButtonState, 50);
} else {
// 无文字时切换功能面板
toggleFuncPanel();
}
});
document.querySelector('.wechat-chat-input-voice')?.addEventListener('click', () => {
hideFuncPanel();
hideEmojiPanel();
showExpandVoice();
});
// 表情按钮
document.querySelector('.wechat-chat-input-emoji')?.addEventListener('click', () => {
hideFuncPanel();
toggleEmojiPanel();
});
initFuncPanel();
initEmojiPanel();
initChatBackground();
initMoments();
initRedPacketEvents();
initTransferEvents();
initGroupRedPacket();
initGiftEvents();
initCropper();
initHistoryEvents();
// 展开面板
document.getElementById('wechat-expand-close')?.addEventListener('click', closeExpandPanel);
document.getElementById('wechat-expand-send')?.addEventListener('click', sendExpandContent);
// 标签栏切换
document.querySelectorAll('.wechat-tab').forEach(tab => {
tab.addEventListener('click', function () {
document.querySelectorAll('.wechat-tab').forEach(t => {
t.classList.toggle('active', t.dataset.tab === this.dataset.tab);
});
const tabName = this.dataset.tab;
if (tabName === 'me') {
showPage('wechat-me-page');
return;
}
if (tabName === 'discover') {
showPage('wechat-discover-page');
return;
}
if (tabName === 'chat') {
showPage('wechat-main-content');
document.getElementById('wechat-chat-tab-content')?.classList.remove('hidden');
document.getElementById('wechat-contacts-tab-content')?.classList.add('hidden');
refreshChatList();
return;
}
if (tabName === 'contacts') {
showPage('wechat-main-content');
document.getElementById('wechat-chat-tab-content')?.classList.add('hidden');
document.getElementById('wechat-contacts-tab-content')?.classList.remove('hidden');
refreshContactsList();
return;
}
showPage('wechat-main-content');
});
});
// 聊天列表项点击(支持单聊和群聊)
document.getElementById('wechat-chat-list')?.addEventListener('click', (e) => {
const chatItem = e.target.closest('.wechat-chat-item');
if (!chatItem) return;
// 检查是否是群聊
if (chatItem.classList.contains('wechat-chat-item-group')) {
const groupIndex = parseInt(chatItem.dataset.groupIndex);
if (!isNaN(groupIndex)) {
import('./group-chat.js').then(m => m.openGroupChat(groupIndex));
}
} else {
// 单聊
const contactId = chatItem.dataset.contactId;
const index = parseInt(chatItem.dataset.index);
if (contactId) openChatByContactId(contactId, index);
}
});
// “我”页面菜单
document.getElementById('wechat-menu-favorites')?.addEventListener('click', () => {
showPage('wechat-favorites-page');
});
document.getElementById('wechat-menu-settings')?.addEventListener('click', () => {
showPage('wechat-settings-page');
});
document.getElementById('wechat-menu-service')?.addEventListener('click', () => {
showPage('wechat-service-page');
updateWalletAmountDisplay();
});
// 服务页返回
document.getElementById('wechat-service-back-btn')?.addEventListener('click', () => {
showPage('wechat-me-page');
});
// 服务页面 - 钱包/上下文开关面板
document.getElementById('wechat-service-wallet')?.addEventListener('click', () => {
document.getElementById('wechat-context-panel')?.classList.add('hidden');
document.getElementById('wechat-wallet-panel')?.classList.toggle('hidden');
});
document.getElementById('wechat-service-context')?.addEventListener('click', () => {
document.getElementById('wechat-wallet-panel')?.classList.add('hidden');
document.getElementById('wechat-context-panel')?.classList.toggle('hidden');
});
// 上下文开关变化
document.getElementById('wechat-context-enabled')?.addEventListener('change', (e) => {
const settings = getSettings();
settings.contextEnabled = e.target.checked;
requestSave();
syncContextEnabledUI(settings.contextEnabled);
});
// 上下文滑块变化
document.getElementById('wechat-context-slider')?.addEventListener('input', (e) => {
const settings = getSettings();
settings.contextLevel = parseInt(e.target.value);
requestSave();
document.getElementById('wechat-context-value').textContent = e.target.value;
});
// 标签容器事件委托(添加/删除)
document.getElementById('wechat-context-tags')?.addEventListener('click', (e) => {
const settings = getSettings();
if (e.target.classList.contains('wechat-tag-del-btn')) {
const index = parseInt(e.target.dataset.index);
if (Array.isArray(settings.contextTags) && index >= 0 && index < settings.contextTags.length) {
settings.contextTags.splice(index, 1);
requestSave();
refreshContextTags();
}
return;
}
if (e.target.classList.contains('wechat-tag-add-btn')) {
const tagName = prompt('输入标签名(如 content、scene):');
if (tagName && tagName.trim()) {
settings.contextTags = Array.isArray(settings.contextTags) ? settings.contextTags : [];
if (!settings.contextTags.includes(tagName.trim())) {
settings.contextTags.push(tagName.trim());
requestSave();
refreshContextTags();
}
}
}
});
// 钱包金额保存(滑出面板)
document.getElementById('wechat-wallet-save-slide')?.addEventListener('click', () => {
const input = document.getElementById('wechat-wallet-input-slide');
const amount = input?.value || '0.00';
const settings = getSettings();
settings.walletAmount = amount;
requestSave();
updateWalletAmountDisplay();
document.getElementById('wechat-wallet-panel')?.classList.add('hidden');
});
// 支付密码保存
document.getElementById('wechat-save-password-btn')?.addEventListener('click', () => {
const input = document.getElementById('wechat-new-password-input');
const password = input?.value || '';
// 验证是否为6位数字
if (!/^\d{6}$/.test(password)) {
showToast('请输入6位数字密码', 'info');
return;
}
const settings = getSettings();
settings.paymentPassword = password;
requestSave();
showToast('密码已保存', '✓');
document.getElementById('wechat-change-password-panel')?.classList.add('hidden');
input.value = '';
});
// 密码输入框只允许数字
document.getElementById('wechat-new-password-input')?.addEventListener('input', (e) => {
e.target.value = e.target.value.replace(/\D/g, '').slice(0, 6);
});
// 总结模板保存
document.getElementById('wechat-summary-template-save')?.addEventListener('click', () => {
const input = document.getElementById('wechat-summary-template-input');
const template = input?.value || '';
const settings = getSettings();
settings.customSummaryTemplate = template;
requestSave();
showToast('模板已保存', '✓');
document.getElementById('wechat-summary-template-panel')?.classList.add('hidden');
});
// 总结模板恢复默认
document.getElementById('wechat-summary-template-reset')?.addEventListener('click', () => {
const input = document.getElementById('wechat-summary-template-input');
if (input) input.value = '';
const settings = getSettings();
settings.customSummaryTemplate = '';
requestSave();
showToast('已恢复默认模板', '✓');
});
// 总结 API 配置
document.getElementById('wechat-summary-key-toggle')?.addEventListener('click', () => {
const input = document.getElementById('wechat-summary-key');
if (input) input.type = input.type === 'password' ? 'text' : 'password';
});
document.getElementById('wechat-summary-fetch-models')?.addEventListener('click', async () => {
const statusEl = document.getElementById('wechat-summary-status');
const url = document.getElementById('wechat-summary-url')?.value?.trim();
const key = document.getElementById('wechat-summary-key')?.value?.trim();
const modelSelect = document.getElementById('wechat-summary-model');
if (!url || !key) {
if (statusEl) statusEl.innerHTML = `${ICON_INFO} 请先填写 URL 和 Key`;
return;
}
if (statusEl) statusEl.textContent = '⏳ 正在获取模型列表...';
try {
const models = await fetchModelListFromApi(url, key);
if (models.length === 0) {
if (statusEl) statusEl.innerHTML = `${ICON_INFO} 未找到可用模型`;
return;
}
if (modelSelect) {
modelSelect.innerHTML = '' +
models.map(m => ``).join('');
}
const settings = getSettings();
settings.summaryModelList = models;
requestSave();
if (statusEl) statusEl.innerHTML = `${ICON_SUCCESS} 获取到 ${models.length} 个模型`;
} catch (err) {
console.error('[可乐] 获取模型列表失败:', err);
if (statusEl) statusEl.textContent = `⚠️ 获取失败: ${err.message}`;
}
});
document.getElementById('wechat-summary-test')?.addEventListener('click', async () => {
const statusEl = document.getElementById('wechat-summary-status');
const url = document.getElementById('wechat-summary-url')?.value?.trim();
const key = document.getElementById('wechat-summary-key')?.value?.trim();
const model = document.getElementById('wechat-summary-model')?.value;
if (!url || !key) {
if (statusEl) statusEl.innerHTML = `${ICON_INFO} 请先填写 URL 和 Key`;
return;
}
if (!model) {
if (statusEl) statusEl.innerHTML = `${ICON_INFO} 请先选择模型`;
return;
}
if (statusEl) statusEl.textContent = '⏳ 正在测试连接...';
try {
const chatUrl = url.replace(/\/+$/, '') + '/chat/completions';
const response = await fetch(chatUrl, {
method: 'POST',
headers: { 'Authorization': `Bearer ${key}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ model, messages: [{ role: 'user', content: 'Hi' }], max_tokens: 5 })
});
if (!response.ok) {
const errData = await response.json().catch(() => ({}));
throw new Error(errData.error?.message || `HTTP ${response.status}`);
}
if (statusEl) statusEl.innerHTML = `${ICON_SUCCESS} 连接成功!`;
} catch (err) {
console.error('[可乐] 测试连接失败:', err);
if (statusEl) statusEl.textContent = `⚠️ 连接失败: ${err.message}`;
}
});
document.getElementById('wechat-summary-save')?.addEventListener('click', () => {
const statusEl = document.getElementById('wechat-summary-status');
const urlInput = document.getElementById('wechat-summary-url');
const keyInput = document.getElementById('wechat-summary-key');
const modelSelect = document.getElementById('wechat-summary-model');
const settings = getSettings();
settings.summaryApiUrl = urlInput?.value?.trim() || '';
settings.summaryApiKey = keyInput?.value?.trim() || '';
settings.summarySelectedModel = modelSelect?.value || '';
requestSave();
if (statusEl) statusEl.innerHTML = `${ICON_SUCCESS} 配置已保存`;
setTimeout(() => document.getElementById('wechat-summary-panel')?.classList.add('hidden'), 1500);
});
document.getElementById('wechat-summary-model')?.addEventListener('change', (e) => {
const settings = getSettings();
settings.summarySelectedModel = e.target.value;
requestSave();
});
document.getElementById('wechat-summary-execute')?.addEventListener('click', () => {
executeSummary();
});
document.getElementById('wechat-summary-rollback')?.addEventListener('click', () => {
rollbackSummary();
});
// 暴露恢复函数到全局,可在控制台调用: window.keleRecoverSummary()
window.keleRecoverSummary = recoverFromTavernWorldbook;
document.getElementById('wechat-summary-close')?.addEventListener('click', () => {
document.getElementById('wechat-summary-panel')?.classList.add('hidden');
});
// 总结面板 - 全选/取消全选
// 刷新按钮
document.getElementById('wechat-summary-refresh')?.addEventListener('click', () => {
refreshSummaryChatList();
});
document.getElementById('wechat-summary-select-all')?.addEventListener('click', () => {
selectAllSummaryChats(true);
});
document.getElementById('wechat-summary-deselect-all')?.addEventListener('click', () => {
selectAllSummaryChats(false);
});
// 发现页面 - 朋友圈点击
document.getElementById('wechat-discover-moments')?.addEventListener('click', () => {
openMomentsPage();
});
// 服务页面 - 服务项点击
document.querySelectorAll('.wechat-service-item').forEach(item => {
item.addEventListener('click', () => {
const service = item.dataset.service;
// 关闭其他面板
const allPanels = ['wechat-context-panel', 'wechat-wallet-panel', 'wechat-summary-panel', 'wechat-history-panel', 'wechat-logs-panel', 'wechat-meme-stickers-panel', 'wechat-change-password-panel', 'wechat-summary-template-panel'];
if (service === 'summary') {
allPanels.filter(p => p !== 'wechat-summary-panel').forEach(p => document.getElementById(p)?.classList.add('hidden'));
const panel = document.getElementById('wechat-summary-panel');
const isHidden = panel?.classList.contains('hidden');
panel?.classList.toggle('hidden');
if (isHidden) {
refreshSummaryChatList();
}
return;
}
if (service === 'history') {
allPanels.filter(p => p !== 'wechat-history-panel').forEach(p => document.getElementById(p)?.classList.add('hidden'));
const panel = document.getElementById('wechat-history-panel');
const isHidden = panel?.classList.contains('hidden');
panel?.classList.toggle('hidden');
if (isHidden) {
refreshHistoryList('all');
}
return;
}
if (service === 'logs') {
allPanels.filter(p => p !== 'wechat-logs-panel').forEach(p => document.getElementById(p)?.classList.add('hidden'));
const panel = document.getElementById('wechat-logs-panel');
const isHidden = panel?.classList.contains('hidden');
panel?.classList.toggle('hidden');
if (isHidden) {
refreshLogsList();
}
return;
}
if (service === 'meme-stickers') {
allPanels.filter(p => p !== 'wechat-meme-stickers-panel').forEach(p => document.getElementById(p)?.classList.add('hidden'));
const panel = document.getElementById('wechat-meme-stickers-panel');
panel?.classList.toggle('hidden');
return;
}
if (service === 'change-password') {
allPanels.filter(p => p !== 'wechat-change-password-panel').forEach(p => document.getElementById(p)?.classList.add('hidden'));
const panel = document.getElementById('wechat-change-password-panel');
panel?.classList.toggle('hidden');
return;
}
if (service === 'summary-template') {
allPanels.filter(p => p !== 'wechat-summary-template-panel').forEach(p => document.getElementById(p)?.classList.add('hidden'));
const panel = document.getElementById('wechat-summary-template-panel');
panel?.classList.toggle('hidden');
return;
}
if (service === 'voice-api') {
allPanels.filter(p => p !== 'wechat-voice-api-panel').forEach(p => document.getElementById(p)?.classList.add('hidden'));
const panel = document.getElementById('wechat-voice-api-panel');
panel?.classList.toggle('hidden');
return;
}
const label = item.querySelector('span')?.textContent || '该';
showToast(`"${label}" 功能开发中...`, 'info');
});
});
// 收藏页面 - 添加按钮根据当前标签显示不同功能
document.getElementById('wechat-favorites-add-btn')?.addEventListener('click', (e) => {
e.stopPropagation();
// 获取当前选中的标签
const activeTab = document.querySelector('.wechat-favorites-tab.active');
const currentFilter = activeTab?.dataset.tab || 'all';
// 根据标签执行不同操作
if (currentFilter === 'user') {
// 用户标签:直接弹出添加用户设定
showAddPersonaPanel();
return;
}
if (currentFilter === 'character') {
// 角色卡标签:显示导入选项
let menu = document.getElementById('wechat-favorites-add-menu');
if (!menu) {
menu = document.createElement('div');
menu.id = 'wechat-favorites-add-menu';
menu.className = 'wechat-dropdown-menu';
menu.style.cssText = 'position: absolute; top: 45px; right: 10px; z-index: 100;';
document.getElementById('wechat-favorites-page')?.appendChild(menu);
}
menu.innerHTML = `
`;
menu.classList.remove('hidden');
menu.querySelector('#wechat-add-menu-import-png')?.addEventListener('click', () => {
menu.classList.add('hidden');
document.getElementById('wechat-file-png')?.click();
});
menu.querySelector('#wechat-add-menu-import-json')?.addEventListener('click', () => {
menu.classList.add('hidden');
document.getElementById('wechat-file-json')?.click();
});
// 点击其他地方关闭菜单
const closeMenu = (ev) => {
if (!ev.target.closest('#wechat-favorites-add-menu') && !ev.target.closest('#wechat-favorites-add-btn')) {
menu.classList.add('hidden');
document.removeEventListener('click', closeMenu);
}
};
setTimeout(() => document.addEventListener('click', closeMenu), 0);
return;
}
if (currentFilter === 'lorebook') {
// 世界书标签:直接弹出添加世界书
showAddLorebookPanel();
return;
}
// 全部标签:显示完整菜单
let menu = document.getElementById('wechat-favorites-add-menu');
if (!menu) {
menu = document.createElement('div');
menu.id = 'wechat-favorites-add-menu';
menu.className = 'wechat-dropdown-menu';
menu.style.cssText = 'position: absolute; top: 45px; right: 10px; z-index: 100;';
document.getElementById('wechat-favorites-page')?.appendChild(menu);
}
menu.innerHTML = `
`;
menu.classList.remove('hidden');
menu.querySelector('#wechat-add-menu-lorebook')?.addEventListener('click', () => {
menu.classList.add('hidden');
showAddLorebookPanel();
});
menu.querySelector('#wechat-add-menu-persona')?.addEventListener('click', () => {
menu.classList.add('hidden');
showAddPersonaPanel();
});
menu.querySelector('#wechat-add-menu-import-png')?.addEventListener('click', () => {
menu.classList.add('hidden');
document.getElementById('wechat-file-png')?.click();
});
menu.querySelector('#wechat-add-menu-import-json')?.addEventListener('click', () => {
menu.classList.add('hidden');
document.getElementById('wechat-file-json')?.click();
});
// 点击其他地方关闭菜单
const closeMenu = (ev) => {
if (!ev.target.closest('#wechat-favorites-add-menu') && !ev.target.closest('#wechat-favorites-add-btn')) {
menu.classList.add('hidden');
document.removeEventListener('click', closeMenu);
}
};
setTimeout(() => document.addEventListener('click', closeMenu), 0);
});
document.getElementById('wechat-lorebook-cancel')?.addEventListener('click', () => {
document.getElementById('wechat-lorebook-modal')?.classList.add('hidden');
});
document.querySelectorAll('.wechat-favorites-tab').forEach(tab => {
tab.addEventListener('click', function () {
document.querySelectorAll('.wechat-favorites-tab').forEach(t => t.classList.remove('active'));
this.classList.add('active');
refreshFavoritesList(this.dataset.tab);
});
});
// 清空联系人
document.getElementById('wechat-clear-contacts')?.addEventListener('click', () => {
if (!confirm('确定要清空所有联系人吗?')) return;
const settings = getSettings();
settings.contacts = [];
requestSave();
refreshContactsList();
showToast('已清空所有联系人');
});
// 用户头像更换
document.getElementById('wechat-me-avatar')?.addEventListener('click', () => {
document.getElementById('wechat-user-avatar-input')?.click();
});
document.getElementById('wechat-user-avatar-input')?.addEventListener('change', async (e) => {
const file = e.target.files[0];
if (!file) return;
try {
const reader = new FileReader();
reader.onload = function (event) {
const settings = getSettings();
settings.userAvatar = event.target.result;
requestSave();
updateMePageInfo();
showToast('头像已更换');
};
reader.readAsDataURL(file);
} catch (err) {
console.error('[可乐] 更换头像失败:', err);
showToast('更换头像失败: ' + err.message, '⚠️');
}
e.target.value = '';
});
// API 配置:密钥可见性
document.getElementById('wechat-toggle-key-visibility')?.addEventListener('click', () => {
const keyInput = document.getElementById('wechat-api-key');
const eyeBtn = document.getElementById('wechat-toggle-key-visibility');
if (!keyInput || !eyeBtn) return;
if (keyInput.type === 'password') {
keyInput.type = 'text';
eyeBtn.innerHTML = '';
} else {
keyInput.type = 'password';
eyeBtn.innerHTML = '';
}
});
// 保存 API 配置
document.getElementById('wechat-save-api')?.addEventListener('click', () => {
const apiUrl = document.getElementById('wechat-api-url')?.value.trim() || '';
const apiKey = document.getElementById('wechat-api-key')?.value.trim() || '';
const selectedModel = document.getElementById('wechat-model-select')?.value || '';
const settings = getSettings();
settings.apiUrl = apiUrl;
settings.apiKey = apiKey;
settings.selectedModel = selectedModel;
requestSave();
showToast('API 配置已保存');
});
// 刷新模型列表
document.getElementById('wechat-refresh-models')?.addEventListener('click', () => {
refreshModelSelect();
});
// 模型选择变化(支持手动输入和从列表选择)
const modelInput = document.getElementById('wechat-model-select');
if (modelInput) {
modelInput.addEventListener('change', (e) => {
const settings = getSettings();
settings.selectedModel = e.target.value.trim();
requestSave();
});
modelInput.addEventListener('input', (e) => {
const settings = getSettings();
settings.selectedModel = e.target.value.trim();
requestSave();
});
}
// 测试 API 连接
document.getElementById('wechat-test-api')?.addEventListener('click', async () => {
const apiUrl = document.getElementById('wechat-api-url')?.value.trim() || '';
const apiKey = document.getElementById('wechat-api-key')?.value.trim() || '';
if (!apiUrl) {
showToast('请先填写 API 地址', 'info');
return;
}
const testBtn = document.getElementById('wechat-test-api');
const originalText = testBtn?.textContent;
if (testBtn) {
testBtn.textContent = '测试中...';
testBtn.disabled = true;
}
try {
await fetchModelListFromApi(apiUrl, apiKey);
showToast('连接成功');
} catch (err) {
showToast('连接失败:' + err.message, '⚠️');
} finally {
if (testBtn) {
testBtn.textContent = originalText;
testBtn.disabled = false;
}
}
});
// 自己填模型按钮 - 单聊
document.getElementById('wechat-manual-model')?.addEventListener('click', () => {
const modelName = prompt('请输入模型名称:');
if (modelName && modelName.trim()) {
const select = document.getElementById('wechat-model-select');
if (select) {
// 添加一个新选项并选中
const option = document.createElement('option');
option.value = modelName.trim();
option.textContent = modelName.trim();
option.selected = true;
select.appendChild(option);
const settings = getSettings();
settings.selectedModel = modelName.trim();
requestSave();
showToast('模型已设置');
}
}
});
// ===== 群聊 API 配置事件 =====
// 群聊密钥可见性
document.getElementById('wechat-toggle-group-key-visibility')?.addEventListener('click', () => {
const keyInput = document.getElementById('wechat-group-api-key');
const eyeBtn = document.getElementById('wechat-toggle-group-key-visibility');
if (!keyInput || !eyeBtn) return;
if (keyInput.type === 'password') {
keyInput.type = 'text';
eyeBtn.innerHTML = '';
} else {
keyInput.type = 'password';
eyeBtn.innerHTML = '';
}
});
// 群聊获取模型列表
document.getElementById('wechat-group-refresh-models')?.addEventListener('click', async () => {
const settings = getSettings();
const apiUrl = document.getElementById('wechat-group-api-url')?.value?.trim() || settings.groupApiUrl || '';
const apiKey = document.getElementById('wechat-group-api-key')?.value?.trim() || settings.groupApiKey || '';
const refreshBtn = document.getElementById('wechat-group-refresh-models');
const select = document.getElementById('wechat-group-model-select');
if (!apiUrl) {
showToast('请先填写群聊 API 地址', 'info');
return;
}
const originalText = refreshBtn?.textContent;
if (refreshBtn) {
refreshBtn.textContent = '加载中...';
refreshBtn.disabled = true;
}
try {
const modelIds = await fetchModelListFromApi(apiUrl, apiKey);
// 更新 select 选项
if (select) {
select.innerHTML = '' +
modelIds.map(id => ``).join('');
}
settings.groupModelList = modelIds;
requestSave();
showToast(`获取到 ${modelIds.length} 个模型`);
} catch (err) {
console.error('[可乐] 获取群聊模型列表失败:', err);
showToast('获取失败,请手动输入模型名', '⚠️');
} finally {
if (refreshBtn) {
refreshBtn.textContent = originalText;
refreshBtn.disabled = false;
}
}
});
// 群聊自己填模型
document.getElementById('wechat-group-manual-model')?.addEventListener('click', () => {
const modelName = prompt('请输入群聊模型名称:');
if (modelName && modelName.trim()) {
const select = document.getElementById('wechat-group-model-select');
if (select) {
// 添加一个新选项并选中
const option = document.createElement('option');
option.value = modelName.trim();
option.textContent = modelName.trim();
option.selected = true;
select.appendChild(option);
const settings = getSettings();
settings.groupSelectedModel = modelName.trim();
requestSave();
showToast('群聊模型已设置');
}
}
});
// 群聊测试连接
document.getElementById('wechat-group-test-api')?.addEventListener('click', async () => {
const apiUrl = document.getElementById('wechat-group-api-url')?.value.trim() || '';
const apiKey = document.getElementById('wechat-group-api-key')?.value.trim() || '';
if (!apiUrl) {
showToast('请先填写群聊 API 地址', 'info');
return;
}
const testBtn = document.getElementById('wechat-group-test-api');
const originalText = testBtn?.textContent;
if (testBtn) {
testBtn.textContent = '测试中...';
testBtn.disabled = true;
}
try {
await fetchModelListFromApi(apiUrl, apiKey);
showToast('群聊 API 连接成功');
} catch (err) {
showToast('连接失败:' + err.message, '⚠️');
} finally {
if (testBtn) {
testBtn.textContent = originalText;
testBtn.disabled = false;
}
}
});
// 保存群聊 API 配置
document.getElementById('wechat-group-save-api')?.addEventListener('click', () => {
const apiUrl = document.getElementById('wechat-group-api-url')?.value.trim() || '';
const apiKey = document.getElementById('wechat-group-api-key')?.value.trim() || '';
const selectedModel = document.getElementById('wechat-group-model-select')?.value || '';
const settings = getSettings();
settings.groupApiUrl = apiUrl;
settings.groupApiKey = apiKey;
settings.groupSelectedModel = selectedModel;
requestSave();
showToast('群聊 API 配置已保存');
});
// 群聊模型选择变化
const groupModelInput = document.getElementById('wechat-group-model-select');
if (groupModelInput) {
groupModelInput.addEventListener('change', (e) => {
const settings = getSettings();
settings.groupSelectedModel = e.target.value.trim();
requestSave();
});
groupModelInput.addEventListener('input', (e) => {
const settings = getSettings();
settings.groupSelectedModel = e.target.value.trim();
requestSave();
});
}
// 总结 API - 自己填模型
document.getElementById('wechat-summary-manual-model')?.addEventListener('click', () => {
const modelName = prompt('请输入总结模型名称:');
if (modelName && modelName.trim()) {
const select = document.getElementById('wechat-summary-model');
if (select) {
// 添加一个新选项并选中
const option = document.createElement('option');
option.value = modelName.trim();
option.textContent = modelName.trim();
option.selected = true;
select.appendChild(option);
const settings = getSettings();
settings.summarySelectedModel = modelName.trim();
requestSave();
showToast('总结模型已设置');
}
}
});
// ===== 历史回顾面板事件 =====
document.getElementById('wechat-history-close')?.addEventListener('click', () => {
document.getElementById('wechat-history-panel')?.classList.add('hidden');
});
// 历史回顾标签切换
document.querySelectorAll('.wechat-history-tab').forEach(tab => {
tab.addEventListener('click', () => {
document.querySelectorAll('.wechat-history-tab').forEach(t => {
t.classList.remove('active', 'wechat-btn-primary');
});
tab.classList.add('active', 'wechat-btn-primary');
refreshHistoryList(tab.dataset.tab);
});
});
// ===== 日志面板事件 =====
document.getElementById('wechat-logs-close')?.addEventListener('click', () => {
document.getElementById('wechat-logs-panel')?.classList.add('hidden');
});
document.getElementById('wechat-logs-clear')?.addEventListener('click', () => {
if (confirm('确定清空所有日志?')) {
clearErrorLogs();
refreshLogsList();
showToast('日志已清空');
}
});
// ===== 语音 API 面板事件 =====
// 关闭按钮
document.getElementById('wechat-voice-api-close')?.addEventListener('click', () => {
document.getElementById('wechat-voice-api-panel')?.classList.add('hidden');
});
// STT 密钥可见性切换
document.getElementById('wechat-stt-key-toggle')?.addEventListener('click', () => {
const keyInput = document.getElementById('wechat-stt-api-key');
if (keyInput) {
keyInput.type = keyInput.type === 'password' ? 'text' : 'password';
}
});
// TTS 密钥可见性切换
document.getElementById('wechat-tts-key-toggle')?.addEventListener('click', () => {
const keyInput = document.getElementById('wechat-tts-api-key');
if (keyInput) {
keyInput.type = keyInput.type === 'password' ? 'text' : 'password';
}
});
// 测试 STT API
document.getElementById('wechat-voice-api-test-stt')?.addEventListener('click', async () => {
const btn = document.getElementById('wechat-voice-api-test-stt');
const originalText = btn?.textContent;
if (btn) {
btn.textContent = '测试中...';
btn.disabled = true;
}
try {
// 先保存当前配置
const settings = getSettings();
settings.sttApiUrl = document.getElementById('wechat-stt-api-url')?.value?.trim() || '';
settings.sttApiKey = document.getElementById('wechat-stt-api-key')?.value?.trim() || '';
settings.sttModel = document.getElementById('wechat-stt-model')?.value?.trim() || '';
await testSttApi();
showToast('STT 连接成功!', '✓');
} catch (err) {
console.error('[可乐] STT 测试失败:', err);
showToast('STT 测试失败: ' + err.message, '⚠️');
} finally {
if (btn) {
btn.textContent = originalText;
btn.disabled = false;
}
}
});
// 测试 TTS API
document.getElementById('wechat-voice-api-test-tts')?.addEventListener('click', async () => {
const btn = document.getElementById('wechat-voice-api-test-tts');
const originalText = btn?.textContent;
if (btn) {
btn.textContent = '测试中...';
btn.disabled = true;
}
try {
// 先保存当前配置
const settings = getSettings();
settings.ttsApiUrl = document.getElementById('wechat-tts-api-url')?.value?.trim() || '';
settings.ttsApiKey = document.getElementById('wechat-tts-api-key')?.value?.trim() || '';
settings.ttsModel = document.getElementById('wechat-tts-model')?.value?.trim() || '';
settings.ttsVoice = document.getElementById('wechat-tts-voice')?.value?.trim() || '';
settings.ttsSpeed = parseFloat(document.getElementById('wechat-tts-speed')?.value) || 1;
settings.ttsEmotion = document.getElementById('wechat-tts-emotion')?.value?.trim() || '默认';
settings.ttsProxyUrl = document.getElementById('wechat-tts-proxy-url')?.value?.trim() || '';
const audioBlob = await testTtsApi();
console.log('[可乐] TTS 测试返回音频:', {
size: audioBlob?.size,
type: audioBlob?.type
});
if (!audioBlob || audioBlob.size < 100) {
throw new Error('返回的音频数据无效');
}
// 播放测试音频
const audioUrl = URL.createObjectURL(audioBlob);
const audio = new Audio(audioUrl);
audio.volume = 1.0;
await new Promise((resolve, reject) => {
audio.onended = () => {
URL.revokeObjectURL(audioUrl);
resolve();
};
audio.onerror = (e) => {
URL.revokeObjectURL(audioUrl);
reject(new Error('音频播放失败'));
};
audio.play().then(() => {
console.log('[可乐] 测试音频开始播放');
}).catch(reject);
});
showToast('TTS 测试成功!正在播放', '✓');
} catch (err) {
console.error('[可乐] TTS 测试失败:', err);
showToast('TTS 测试失败: ' + err.message, '⚠️');
} finally {
if (btn) {
btn.textContent = originalText;
btn.disabled = false;
}
}
});
// 保存语音 API 配置
document.getElementById('wechat-voice-api-save')?.addEventListener('click', () => {
const settings = getSettings();
// STT 配置
settings.sttApiUrl = document.getElementById('wechat-stt-api-url')?.value?.trim() || '';
settings.sttApiKey = document.getElementById('wechat-stt-api-key')?.value?.trim() || '';
settings.sttModel = document.getElementById('wechat-stt-model')?.value?.trim() || '';
// TTS 配置
settings.ttsApiUrl = document.getElementById('wechat-tts-api-url')?.value?.trim() || '';
settings.ttsApiKey = document.getElementById('wechat-tts-api-key')?.value?.trim() || '';
settings.ttsModel = document.getElementById('wechat-tts-model')?.value?.trim() || '';
settings.ttsVoice = document.getElementById('wechat-tts-voice')?.value?.trim() || '';
settings.ttsSpeed = parseFloat(document.getElementById('wechat-tts-speed')?.value) || 1;
settings.ttsEmotion = document.getElementById('wechat-tts-emotion')?.value?.trim() || '默认';
settings.ttsProxyUrl = document.getElementById('wechat-tts-proxy-url')?.value?.trim() || '';
requestSave();
showToast('语音 API 配置已保存', '✓');
});
// 绑定联系人点击
refreshContactsList();
}
function init() {
loadSettings();
const settings = getSettings();
if (seedDefaultUserPersonaFromST(settings)) {
requestSave();
}
const phoneHTML = generatePhoneHTML();
document.body.insertAdjacentHTML('beforeend', phoneHTML);
setupPhoneAutoCentering();
setupPhoneDrag();
bindEvents();
// 初始化发送按钮状态
window.updateSendButtonState?.();
// 初始化底部导航栏红点
updateTabBadge();
restoreModelSelect();
restoreGroupModelSelect();
// 同步上下文面板初始 UI
syncContextEnabledUI(settings.contextEnabled);
refreshContextTags();
updateWalletAmountDisplay();
if (settings.autoInjectPrompt) {
injectAuthorNote();
}
setupMessageObserver();
addExtensionButton();
// 初始化错误捕获
initErrorCapture();
// 初始化页面卸载保存
setupUnloadSave();
setInterval(() => {
const phone = document.getElementById('wechat-phone');
if (!phone || phone.classList.contains('hidden')) return;
const timeEl = document.querySelector('.wechat-statusbar-time');
if (timeEl) timeEl.textContent = getCurrentTime();
}, 60000);
// 首次可见时居中
centerPhoneInViewport({ force: true });
// 初始化悬浮球
createFloatingBall();
// 根据设置决定是否显示
if (settings.floatingBallEnabled === false) {
hideFloatingBall();
}
updateFloatingBallMenuText(settings.floatingBallEnabled !== false);
console.log('✅ 可乐不加冰 已加载');
}
if (typeof jQuery === 'function') {
jQuery(() => setTimeout(init, 500));
} else {
document.addEventListener('DOMContentLoaded', () => setTimeout(init, 500), { once: true });
}