2766 lines
106 KiB
Plaintext
2766 lines
106 KiB
Plaintext
|
|
/* ===== 云大阁网站JavaScript - 修复版 ===== */
|
|||
|
|
|
|||
|
|
// 全局变量
|
|||
|
|
let chatCounter = 0;
|
|||
|
|
let isDeepSearchActive = false;
|
|||
|
|
let currentChatId = null;
|
|||
|
|
let chatHistoryData = new Map(); // 存储聊天历史数据
|
|||
|
|
let isOnlineMode = false; // 是否使用在线模式(连接后端),默认false使用本地存储
|
|||
|
|
|
|||
|
|
// DOM加载完成后执行
|
|||
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|||
|
|
// 添加ESC键监听器以支持中断请求
|
|||
|
|
document.addEventListener('keydown', function(e) {
|
|||
|
|
if (e.key === 'Escape' && window.isSubmitting) {
|
|||
|
|
console.log('用户按下ESC键,中断请求');
|
|||
|
|
|
|||
|
|
// 中断请求
|
|||
|
|
if (window.currentAbortController) {
|
|||
|
|
window.currentAbortController.abort();
|
|||
|
|
window.currentAbortController = null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 重置状态
|
|||
|
|
window.isSubmitting = false;
|
|||
|
|
if (window.updateSendUI) {
|
|||
|
|
window.updateSendUI(false);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 显示提示
|
|||
|
|
const statusText = document.querySelector('.status-text');
|
|||
|
|
if (statusText) {
|
|||
|
|
statusText.textContent = '已取消请求(ESC)';
|
|||
|
|
statusText.style.color = '#dc3545';
|
|||
|
|
setTimeout(() => {
|
|||
|
|
statusText.textContent = 'Ready to submit!';
|
|||
|
|
statusText.style.color = '';
|
|||
|
|
}, 3000);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 在聊天窗口添加提示
|
|||
|
|
const chatContent = document.getElementById('chatContent');
|
|||
|
|
const chatMessages = document.getElementById('chatMessages');
|
|||
|
|
if (chatContent || chatMessages) {
|
|||
|
|
const targetElement = chatMessages || chatContent;
|
|||
|
|
const warningDiv = document.createElement('div');
|
|||
|
|
warningDiv.className = 'ai-message';
|
|||
|
|
warningDiv.style.cssText = 'background-color: #fff3cd; border: 1px solid #ffc107; color: #856404; padding: 10px; border-radius: 8px; margin: 10px 0;';
|
|||
|
|
warningDiv.innerHTML = '⚠️ 请求已被ESC键取消。注意:后端可能仍在处理,建议稍等片刻再发送新请求。';
|
|||
|
|
targetElement.appendChild(warningDiv);
|
|||
|
|
setTimeout(() => {
|
|||
|
|
warningDiv.style.transition = 'opacity 0.5s';
|
|||
|
|
warningDiv.style.opacity = '0';
|
|||
|
|
setTimeout(() => warningDiv.remove(), 500);
|
|||
|
|
}, 5000);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 只初始化核心功能,避免冲突
|
|||
|
|
initSidebar();
|
|||
|
|
initChat();
|
|||
|
|
// initUserDropdown(); // 注释掉,由auth-integration.js处理
|
|||
|
|
initSearch();
|
|||
|
|
initChatHistoryActions();
|
|||
|
|
initTextRotator();
|
|||
|
|
init3DTicker();
|
|||
|
|
initDownloadModal();
|
|||
|
|
initBackground(); // 初始化背景动画
|
|||
|
|
initToolsDropdown(); // 初始化工具下拉菜单
|
|||
|
|
|
|||
|
|
// 确保初始状态下3D图片是显示的
|
|||
|
|
const tickerSection = document.querySelector('.ticker-section');
|
|||
|
|
const ticker3D = document.getElementById('ticker3D');
|
|||
|
|
|
|||
|
|
console.log('检查ticker-section:', tickerSection);
|
|||
|
|
console.log('检查ticker3D:', ticker3D);
|
|||
|
|
console.log('body是否有chat-mode类:', document.body.classList.contains('chat-mode'));
|
|||
|
|
|
|||
|
|
if (tickerSection) {
|
|||
|
|
tickerSection.style.display = 'block';
|
|||
|
|
tickerSection.style.visibility = 'visible';
|
|||
|
|
tickerSection.style.opacity = '1';
|
|||
|
|
console.log('设置ticker-section显示');
|
|||
|
|
|
|||
|
|
// 检查computed style
|
|||
|
|
const computedStyle = window.getComputedStyle(tickerSection);
|
|||
|
|
console.log('ticker-section computed display:', computedStyle.display);
|
|||
|
|
console.log('ticker-section computed visibility:', computedStyle.visibility);
|
|||
|
|
console.log('ticker-section computed height:', computedStyle.height);
|
|||
|
|
} else {
|
|||
|
|
console.error('未找到ticker-section元素');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 设置侧边栏默认状态 - 默认折叠
|
|||
|
|
const sidebar = document.getElementById('sidebar');
|
|||
|
|
|
|||
|
|
if (sidebar) {
|
|||
|
|
// 所有设备默认折叠状态
|
|||
|
|
sidebar.classList.add('collapsed');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 页面加载完自动聚焦到输入框
|
|||
|
|
const messageInput = document.getElementById('messageInput');
|
|||
|
|
if (messageInput) {
|
|||
|
|
setTimeout(() => {
|
|||
|
|
messageInput.focus();
|
|||
|
|
}, 500);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 初始化聊天管理器监听器
|
|||
|
|
initChatManagerListeners();
|
|||
|
|
|
|||
|
|
// 等待 ChatManager 初始化完成后再加载
|
|||
|
|
const waitForChatManager = () => {
|
|||
|
|
if (window.chatManager && window.chatManager.initialized) {
|
|||
|
|
console.log('[Main] ChatManager 已初始化,开始加载聊天历史');
|
|||
|
|
|
|||
|
|
// 加载用户的聊天历史
|
|||
|
|
loadUserChatHistory();
|
|||
|
|
|
|||
|
|
// 恢复当前对话
|
|||
|
|
setTimeout(() => {
|
|||
|
|
console.log('[Main] 检查是否需要恢复对话...');
|
|||
|
|
console.log('[Main] chatManager.currentChatId:', window.chatManager.currentChatId);
|
|||
|
|
|
|||
|
|
if (window.chatManager.currentChatId) {
|
|||
|
|
console.log('[Main] 开始恢复上次的对话:', window.chatManager.currentChatId);
|
|||
|
|
loadChatHistory(window.chatManager.currentChatId);
|
|||
|
|
|
|||
|
|
// 高亮侧边栏中的当前对话
|
|||
|
|
const chatItem = document.querySelector(`[data-chat-id="${window.chatManager.currentChatId}"]`);
|
|||
|
|
if (chatItem) {
|
|||
|
|
chatItem.classList.add('active');
|
|||
|
|
console.log('[Main] 已高亮当前对话项');
|
|||
|
|
} else {
|
|||
|
|
console.log('[Main] ⚠️ 未找到对应的侧边栏对话项');
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
console.log('[Main] 没有需要恢复的对话');
|
|||
|
|
}
|
|||
|
|
}, 50);
|
|||
|
|
} else {
|
|||
|
|
console.log('[Main] 等待 ChatManager 初始化...');
|
|||
|
|
setTimeout(waitForChatManager, 100);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
waitForChatManager();
|
|||
|
|
|
|||
|
|
// 监听用户登录状态变化
|
|||
|
|
initUserChangeListener();
|
|||
|
|
|
|||
|
|
// 初始化Markdown渲染器和工具栏
|
|||
|
|
if (window.markdownRenderer) {
|
|||
|
|
window.markdownRenderer.initCodeCopyButtons();
|
|||
|
|
|
|||
|
|
// 确保工具栏被初始化
|
|||
|
|
const welcomeToolbar = document.getElementById('markdownToolbar');
|
|||
|
|
const welcomeInput = document.getElementById('messageInput');
|
|||
|
|
if (welcomeToolbar && welcomeInput) {
|
|||
|
|
welcomeToolbar.innerHTML = window.markdownRenderer.createToolbar();
|
|||
|
|
welcomeToolbar.style.display = 'flex';
|
|||
|
|
window.markdownRenderer.attachToolbarEvents(welcomeToolbar, welcomeInput);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 初始化工具下拉菜单
|
|||
|
|
initToolsDropdown();
|
|||
|
|
|
|||
|
|
console.log('云大阁网站已加载完成');
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
/* ===== 侧边栏功能 - 复刻duijie.html简单逻辑 + yundage-keyong1的tooltip ===== */
|
|||
|
|
function toggleSidebar() {
|
|||
|
|
document.getElementById('sidebar').classList.toggle('collapsed');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function initSidebar() {
|
|||
|
|
// Logo点击切换事件
|
|||
|
|
const logoMenuItem = document.getElementById('logoMenuItem');
|
|||
|
|
if (logoMenuItem) {
|
|||
|
|
logoMenuItem.addEventListener('click', function(e) {
|
|||
|
|
toggleSidebar();
|
|||
|
|
e.stopPropagation();
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 新建对话按钮事件
|
|||
|
|
const newChatBtn = document.getElementById('newChatBtn');
|
|||
|
|
if (newChatBtn) {
|
|||
|
|
newChatBtn.addEventListener('click', function() {
|
|||
|
|
console.log('新建对话按钮被点击');
|
|||
|
|
startNewChat();
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 下载APP按钮事件
|
|||
|
|
const downloadAppBtn = document.getElementById('downloadAppBtn');
|
|||
|
|
if (downloadAppBtn) {
|
|||
|
|
downloadAppBtn.addEventListener('click', function() {
|
|||
|
|
console.log('下载APP按钮被点击');
|
|||
|
|
initDownloadModal();
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 初始化tooltip定位
|
|||
|
|
initTooltipPositioning();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ChatGPT风格的tooltip定位 - 将tooltip移动到body避免遮挡
|
|||
|
|
function initTooltipPositioning() {
|
|||
|
|
const sidebar = document.getElementById('sidebar');
|
|||
|
|
if (!sidebar) return; // 如果sidebar不存在,直接返回
|
|||
|
|
|
|||
|
|
const tooltipWrappers = sidebar.querySelectorAll('.tooltip-wrapper');
|
|||
|
|
|
|||
|
|
tooltipWrappers.forEach(wrapper => {
|
|||
|
|
const tooltipText = wrapper.querySelector('.tooltip-text');
|
|||
|
|
if (!tooltipText) return;
|
|||
|
|
|
|||
|
|
let floatingTooltip = null;
|
|||
|
|
|
|||
|
|
wrapper.addEventListener('mouseenter', function() {
|
|||
|
|
if (sidebar.classList.contains('collapsed')) {
|
|||
|
|
// 创建独立的tooltip元素添加到body
|
|||
|
|
floatingTooltip = document.createElement('div');
|
|||
|
|
floatingTooltip.textContent = tooltipText.textContent;
|
|||
|
|
floatingTooltip.className = 'floating-tooltip';
|
|||
|
|
floatingTooltip.style.cssText = `
|
|||
|
|
position: fixed;
|
|||
|
|
background-color: #333;
|
|||
|
|
color: #fff;
|
|||
|
|
text-align: center;
|
|||
|
|
padding: 6px 10px;
|
|||
|
|
border-radius: 6px;
|
|||
|
|
white-space: nowrap;
|
|||
|
|
font-size: 13px;
|
|||
|
|
z-index: 999999;
|
|||
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
|||
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|||
|
|
pointer-events: none;
|
|||
|
|
`;
|
|||
|
|
|
|||
|
|
// 获取wrapper的位置
|
|||
|
|
const rect = wrapper.getBoundingClientRect();
|
|||
|
|
|
|||
|
|
// 设置tooltip位置
|
|||
|
|
floatingTooltip.style.left = (rect.right + 10) + 'px';
|
|||
|
|
floatingTooltip.style.top = (rect.top + rect.height / 2) + 'px';
|
|||
|
|
floatingTooltip.style.transform = 'translateY(-50%)';
|
|||
|
|
|
|||
|
|
// 添加到body
|
|||
|
|
document.body.appendChild(floatingTooltip);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
wrapper.addEventListener('mouseleave', function() {
|
|||
|
|
if (floatingTooltip) {
|
|||
|
|
document.body.removeChild(floatingTooltip);
|
|||
|
|
floatingTooltip = null;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* ===== 聊天功能 ===== */
|
|||
|
|
function initChat() {
|
|||
|
|
const sendBtn = document.getElementById('sendBtn');
|
|||
|
|
const messageInput = document.getElementById('messageInput');
|
|||
|
|
const chatMessages = document.getElementById('chatMessages');
|
|||
|
|
const deepSearchToggle = document.getElementById('deepSearchToggle');
|
|||
|
|
const newChatBtn = document.getElementById('newChatBtn');
|
|||
|
|
const voiceBtn = document.getElementById('voiceBtn');
|
|||
|
|
|
|||
|
|
// 聊天模式元素
|
|||
|
|
const chatModeSendBtn = document.getElementById('chatModeSendBtn');
|
|||
|
|
const chatModeMessageInput = document.getElementById('chatModeMessageInput');
|
|||
|
|
|
|||
|
|
if (!sendBtn || !messageInput || !chatMessages) {
|
|||
|
|
console.error('关键聊天元素未找到');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 初始化发送按钮状态
|
|||
|
|
updateSendButtonState();
|
|||
|
|
|
|||
|
|
// 发送消息(支持停止功能)
|
|||
|
|
sendBtn.addEventListener('click', function(e) {
|
|||
|
|
e.preventDefault();
|
|||
|
|
|
|||
|
|
if (window.isSubmitting) {
|
|||
|
|
// 停止操作
|
|||
|
|
console.log('用户点击停止按钮');
|
|||
|
|
if (window.currentAbortController) {
|
|||
|
|
window.currentAbortController.abort();
|
|||
|
|
window.currentAbortController = null;
|
|||
|
|
}
|
|||
|
|
window.isSubmitting = false;
|
|||
|
|
updateSendUI(false);
|
|||
|
|
|
|||
|
|
// 显示提示信息
|
|||
|
|
const statusText = document.querySelector('.status-text');
|
|||
|
|
if (statusText) {
|
|||
|
|
statusText.textContent = '已取消请求';
|
|||
|
|
statusText.style.color = '#dc3545';
|
|||
|
|
setTimeout(() => {
|
|||
|
|
statusText.textContent = 'Ready to submit!';
|
|||
|
|
statusText.style.color = '';
|
|||
|
|
}, 3000);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 在聊天窗口添加提示
|
|||
|
|
const chatContent = document.getElementById('chatContent');
|
|||
|
|
if (chatContent) {
|
|||
|
|
const warningDiv = document.createElement('div');
|
|||
|
|
warningDiv.className = 'ai-message';
|
|||
|
|
warningDiv.style.cssText = 'background-color: #fff3cd; border: 1px solid #ffc107; color: #856404; padding: 10px; border-radius: 8px; margin: 10px 0;';
|
|||
|
|
warningDiv.innerHTML = '⚠️ 请求已取消。注意:后端可能仍在处理,建议稍等片刻再发送新请求。';
|
|||
|
|
chatContent.appendChild(warningDiv);
|
|||
|
|
setTimeout(() => {
|
|||
|
|
warningDiv.style.transition = 'opacity 0.5s';
|
|||
|
|
warningDiv.style.opacity = '0';
|
|||
|
|
setTimeout(() => warningDiv.remove(), 500);
|
|||
|
|
}, 5000);
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
// 发送操作
|
|||
|
|
console.log('发送按钮被点击');
|
|||
|
|
sendMessage();
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 回车发送消息(Shift+Enter换行)
|
|||
|
|
messageInput.addEventListener('keydown', function(e) {
|
|||
|
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|||
|
|
e.preventDefault();
|
|||
|
|
sendMessage();
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 监听输入框变化,更新发送按钮状态和自动调整高度
|
|||
|
|
messageInput.addEventListener('input', function() {
|
|||
|
|
updateSendButtonState();
|
|||
|
|
autoResizeTextarea(this);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 深度检索切换
|
|||
|
|
if (deepSearchToggle) {
|
|||
|
|
deepSearchToggle.addEventListener('click', function() {
|
|||
|
|
console.log('深度检索按钮被点击');
|
|||
|
|
toggleDeepSearch();
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 删除重复的事件监听器绑定,前面已经绑定过了
|
|||
|
|
|
|||
|
|
// 语音按钮(暂时无功能)
|
|||
|
|
if (voiceBtn) {
|
|||
|
|
voiceBtn.addEventListener('click', function() {
|
|||
|
|
console.log('语音按钮被点击');
|
|||
|
|
// 这里可以添加语音功能
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 聊天模式输入框事件监听器
|
|||
|
|
if (chatModeSendBtn && chatModeMessageInput) {
|
|||
|
|
// 发送消息(支持停止功能)
|
|||
|
|
chatModeSendBtn.addEventListener('click', function(e) {
|
|||
|
|
e.preventDefault();
|
|||
|
|
|
|||
|
|
if (window.isSubmitting) {
|
|||
|
|
// 停止操作
|
|||
|
|
console.log('用户点击停止按钮(聊天模式)');
|
|||
|
|
if (window.currentAbortController) {
|
|||
|
|
window.currentAbortController.abort();
|
|||
|
|
window.currentAbortController = null;
|
|||
|
|
}
|
|||
|
|
window.isSubmitting = false;
|
|||
|
|
updateSendUI(false);
|
|||
|
|
|
|||
|
|
// 在聊天窗口添加提示
|
|||
|
|
addMessage('⚠️ 请求已取消。注意:后端可能仍在处理,建议稍等片刻再发送新请求。', 'system');
|
|||
|
|
} else {
|
|||
|
|
// 发送操作
|
|||
|
|
sendChatModeMessage();
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 回车发送消息
|
|||
|
|
chatModeMessageInput.addEventListener('keydown', function(e) {
|
|||
|
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|||
|
|
e.preventDefault();
|
|||
|
|
sendChatModeMessage();
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 监听输入框变化
|
|||
|
|
chatModeMessageInput.addEventListener('input', function() {
|
|||
|
|
updateChatModeSendButtonState();
|
|||
|
|
autoResizeTextarea(this);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新发送按钮状态
|
|||
|
|
function updateSendButtonState() {
|
|||
|
|
const messageInput = document.getElementById('messageInput');
|
|||
|
|
const sendBtn = document.getElementById('sendBtn');
|
|||
|
|
const sendIcon = document.getElementById('sendIcon');
|
|||
|
|
|
|||
|
|
if (messageInput && sendBtn && sendIcon) {
|
|||
|
|
const hasText = messageInput.value.trim().length > 0;
|
|||
|
|
sendIcon.style.opacity = hasText && !window.isSubmitting ? '1' : '0.3';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新发送UI状态(改造为停止按钮)
|
|||
|
|
function updateSendUI(isLoading) {
|
|||
|
|
const sendIcon = document.getElementById('sendIcon');
|
|||
|
|
const loadingSpinner = document.getElementById('loadingSpinner');
|
|||
|
|
const statusText = document.querySelector('.status-text');
|
|||
|
|
const messageInput = document.getElementById('messageInput');
|
|||
|
|
const sendBtn = document.getElementById('sendBtn');
|
|||
|
|
|
|||
|
|
// 同时更新聊天模式的UI
|
|||
|
|
const chatModeSendIcon = document.getElementById('chatModeSendIcon');
|
|||
|
|
const chatModeLoadingSpinner = document.getElementById('chatModeLoadingSpinner');
|
|||
|
|
const chatModeMessageInput = document.getElementById('chatModeMessageInput');
|
|||
|
|
const chatModeSendBtn = document.getElementById('chatModeSendBtn');
|
|||
|
|
|
|||
|
|
if (isLoading) {
|
|||
|
|
// 首页:显示红色停止图标
|
|||
|
|
if (loadingSpinner) {
|
|||
|
|
loadingSpinner.style.setProperty('display', 'none', 'important');
|
|||
|
|
}
|
|||
|
|
if (sendIcon) {
|
|||
|
|
sendIcon.style.display = 'block';
|
|||
|
|
sendIcon.innerHTML = `
|
|||
|
|
<rect x="6" y="6" width="12" height="12" fill="#dc3545"/>
|
|||
|
|
`;
|
|||
|
|
sendIcon.style.opacity = '1';
|
|||
|
|
}
|
|||
|
|
if (sendBtn) {
|
|||
|
|
sendBtn.disabled = false;
|
|||
|
|
sendBtn.title = '点击停止(或按ESC键)';
|
|||
|
|
}
|
|||
|
|
if (statusText) statusText.textContent = 'AI is thinking...';
|
|||
|
|
if (messageInput) messageInput.disabled = true;
|
|||
|
|
|
|||
|
|
// 聊天模式:同样显示红色停止图标
|
|||
|
|
if (chatModeLoadingSpinner) {
|
|||
|
|
chatModeLoadingSpinner.style.setProperty('display', 'none', 'important');
|
|||
|
|
}
|
|||
|
|
if (chatModeSendIcon) {
|
|||
|
|
chatModeSendIcon.style.display = 'block';
|
|||
|
|
chatModeSendIcon.innerHTML = `
|
|||
|
|
<rect x="6" y="6" width="12" height="12" fill="#dc3545"/>
|
|||
|
|
`;
|
|||
|
|
chatModeSendIcon.style.opacity = '1';
|
|||
|
|
}
|
|||
|
|
if (chatModeSendBtn) {
|
|||
|
|
chatModeSendBtn.disabled = false;
|
|||
|
|
chatModeSendBtn.title = '点击停止(或按ESC键)';
|
|||
|
|
}
|
|||
|
|
if (chatModeMessageInput) chatModeMessageInput.disabled = true;
|
|||
|
|
|
|||
|
|
} else {
|
|||
|
|
// 首页:恢复发送图标
|
|||
|
|
if (sendIcon) {
|
|||
|
|
sendIcon.style.display = 'block';
|
|||
|
|
sendIcon.innerHTML = `
|
|||
|
|
<path d="m3 3 3 9-3 9 19-9Z"/>
|
|||
|
|
<path d="m6 12 13 0"/>
|
|||
|
|
`;
|
|||
|
|
}
|
|||
|
|
if (loadingSpinner) {
|
|||
|
|
loadingSpinner.style.display = 'none';
|
|||
|
|
}
|
|||
|
|
if (sendBtn) {
|
|||
|
|
sendBtn.disabled = false;
|
|||
|
|
sendBtn.title = '发送消息';
|
|||
|
|
}
|
|||
|
|
if (statusText) statusText.textContent = 'Ready to submit!';
|
|||
|
|
if (messageInput) messageInput.disabled = false;
|
|||
|
|
updateSendButtonState();
|
|||
|
|
|
|||
|
|
// 聊天模式:恢复发送图标
|
|||
|
|
if (chatModeSendIcon) {
|
|||
|
|
chatModeSendIcon.style.display = 'block';
|
|||
|
|
chatModeSendIcon.innerHTML = `
|
|||
|
|
<path d="m3 3 3 9-3 9 19-9Z"/>
|
|||
|
|
<path d="m6 12 13 0"/>
|
|||
|
|
`;
|
|||
|
|
}
|
|||
|
|
if (chatModeLoadingSpinner) {
|
|||
|
|
chatModeLoadingSpinner.style.display = 'none';
|
|||
|
|
}
|
|||
|
|
if (chatModeSendBtn) {
|
|||
|
|
chatModeSendBtn.disabled = false;
|
|||
|
|
chatModeSendBtn.title = '发送消息';
|
|||
|
|
}
|
|||
|
|
if (chatModeMessageInput) chatModeMessageInput.disabled = false;
|
|||
|
|
updateChatModeSendButtonState();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 暴露到全局以便其他模块使用
|
|||
|
|
window.updateSendUI = updateSendUI;
|
|||
|
|
|
|||
|
|
// 聊天模式按钮状态管理
|
|||
|
|
function updateChatModeSendButtonState() {
|
|||
|
|
const chatModeMessageInput = document.getElementById('chatModeMessageInput');
|
|||
|
|
const chatModeSendIcon = document.getElementById('chatModeSendIcon');
|
|||
|
|
|
|||
|
|
if (chatModeMessageInput && chatModeSendIcon) {
|
|||
|
|
const hasText = chatModeMessageInput.value.trim().length > 0;
|
|||
|
|
chatModeSendIcon.style.opacity = hasText && !window.isSubmitting ? '1' : '0.3';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 聊天模式发送消息
|
|||
|
|
async function sendChatModeMessage() {
|
|||
|
|
const chatModeMessageInput = document.getElementById('chatModeMessageInput');
|
|||
|
|
const message = chatModeMessageInput.value.trim();
|
|||
|
|
|
|||
|
|
if (!message || window.isSubmitting) {
|
|||
|
|
console.log('消息为空或正在发送中,不发送');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log('聊天模式发送消息:', message);
|
|||
|
|
|
|||
|
|
// 设置发送状态
|
|||
|
|
window.isSubmitting = true;
|
|||
|
|
updateChatModeSendUI(true);
|
|||
|
|
|
|||
|
|
// 如果是第一条消息且没有当前聊天ID,创建新聊天
|
|||
|
|
if (!currentChatId) {
|
|||
|
|
// 立即创建新的聊天会话
|
|||
|
|
if (window.chatManager) {
|
|||
|
|
currentChatId = window.chatManager.createChat(message);
|
|||
|
|
window.chatManager.currentChatId = currentChatId;
|
|||
|
|
|
|||
|
|
// 立即添加到侧边栏
|
|||
|
|
const chatTitle = message.length > 20 ? message.substring(0, 20) + '...' : message;
|
|||
|
|
addChatToSidebar(currentChatId, chatTitle, true);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 保存当前聊天ID,避免切换聊天记录时被改变
|
|||
|
|
const targetChatId = currentChatId;
|
|||
|
|
|
|||
|
|
// 添加用户消息
|
|||
|
|
await addMessage(message, 'user');
|
|||
|
|
|
|||
|
|
// 清空输入框
|
|||
|
|
chatModeMessageInput.value = '';
|
|||
|
|
autoResizeTextarea(chatModeMessageInput);
|
|||
|
|
updateChatModeSendButtonState();
|
|||
|
|
|
|||
|
|
// 调用后端API获取AI回复
|
|||
|
|
try {
|
|||
|
|
// 判断是否使用流式响应
|
|||
|
|
const useStream = true; // 可以根据需要调整
|
|||
|
|
|
|||
|
|
if (useStream) {
|
|||
|
|
// 流式响应
|
|||
|
|
let aiMessageId = null;
|
|||
|
|
let aiContent = '';
|
|||
|
|
|
|||
|
|
// 获取工具设置
|
|||
|
|
const toolsSettings = window.getToolsSettings ? window.getToolsSettings() : { deepResearch: false, showThinking: false };
|
|||
|
|
const isDeepResearch = toolsSettings.deepResearch;
|
|||
|
|
const showThinking = toolsSettings.showThinking;
|
|||
|
|
|
|||
|
|
// 创建状态显示组件
|
|||
|
|
let statusDisplay = null;
|
|||
|
|
if (window.StreamStatusDisplay) {
|
|||
|
|
// 在消息区域之前创建状态显示容器
|
|||
|
|
const statusContainer = document.createElement('div');
|
|||
|
|
statusContainer.id = `status-${targetChatId}-${Date.now()}`;
|
|||
|
|
chatMessages.appendChild(statusContainer);
|
|||
|
|
statusDisplay = new window.StreamStatusDisplay(statusContainer);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const abortController = await window.ChatAPIService.sendMessageStream(
|
|||
|
|
window.currentAbortController = abortController; // 保存到全局以便中断
|
|||
|
|
{
|
|||
|
|
message: message,
|
|||
|
|
conversationId: targetChatId, // 使用保存的targetChatId
|
|||
|
|
mode: isDeepSearchActive ? 'research' : 'chat',
|
|||
|
|
deepResearch: isDeepResearch,
|
|||
|
|
showThinking: showThinking
|
|||
|
|
},
|
|||
|
|
// onMessage: 接收消息片段
|
|||
|
|
(content) => {
|
|||
|
|
// 只有当前聊天ID与目标ID一致时才更新UI
|
|||
|
|
if (currentChatId === targetChatId) {
|
|||
|
|
aiContent += content;
|
|||
|
|
if (aiMessageId) {
|
|||
|
|
// 更新已存在的消息
|
|||
|
|
updateMessageContent(aiMessageId, aiContent);
|
|||
|
|
} else {
|
|||
|
|
// 创建新消息
|
|||
|
|
aiMessageId = Date.now().toString();
|
|||
|
|
addStreamMessage(aiContent, 'ai', aiMessageId);
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
// 如果已切换到其他聊天,仅累积内容但不更新UI
|
|||
|
|
aiContent += content;
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
// onComplete: 完成回调
|
|||
|
|
async (data) => {
|
|||
|
|
console.log('流式响应完成:', data);
|
|||
|
|
// 使用保存的targetChatId保存AI回复到正确的聊天记录
|
|||
|
|
if (window.chatManager && targetChatId) {
|
|||
|
|
// 保存到目标聊天记录
|
|||
|
|
const originalChatId = window.chatManager.currentChatId;
|
|||
|
|
window.chatManager.currentChatId = targetChatId;
|
|||
|
|
window.chatManager.addMessage(aiContent, 'ai');
|
|||
|
|
// 如果当前聊天已切换,不需要恢复
|
|||
|
|
if (currentChatId !== targetChatId) {
|
|||
|
|
window.chatManager.currentChatId = originalChatId;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// 恢复发送状态
|
|||
|
|
window.isSubmitting = false;
|
|||
|
|
updateChatModeSendUI(false);
|
|||
|
|
},
|
|||
|
|
// onError: 错误回调
|
|||
|
|
(error) => {
|
|||
|
|
console.error('流式响应错误:', error);
|
|||
|
|
addMessage(`抱歉,发生了错误:${error.message}`, 'ai');
|
|||
|
|
window.isSubmitting = false;
|
|||
|
|
updateChatModeSendUI(false);
|
|||
|
|
},
|
|||
|
|
// onThinking: 思考过程回调
|
|||
|
|
showThinking ? (thinking) => {
|
|||
|
|
console.log('思考过程:', thinking);
|
|||
|
|
// 显示思考过程
|
|||
|
|
if (!document.getElementById('thinking-message')) {
|
|||
|
|
const thinkingDiv = document.createElement('div');
|
|||
|
|
thinkingDiv.id = 'thinking-message';
|
|||
|
|
thinkingDiv.className = 'thinking-process';
|
|||
|
|
thinkingDiv.innerHTML = `
|
|||
|
|
<div class="thinking-label">🤔 思考过程:</div>
|
|||
|
|
<div class="thinking-content"></div>
|
|||
|
|
`;
|
|||
|
|
chatMessages.appendChild(thinkingDiv);
|
|||
|
|
chatMessages.scrollTop = chatMessages.scrollHeight;
|
|||
|
|
}
|
|||
|
|
const thinkingContent = document.querySelector('#thinking-message .thinking-content');
|
|||
|
|
if (thinkingContent) {
|
|||
|
|
thinkingContent.textContent += thinking;
|
|||
|
|
}
|
|||
|
|
} : null,
|
|||
|
|
// onStatus: 状态更新回调(新增)
|
|||
|
|
(status) => {
|
|||
|
|
if (statusDisplay) {
|
|||
|
|
statusDisplay.updateStatus(status);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
} else {
|
|||
|
|
// 同步响应
|
|||
|
|
const response = await window.ChatAPIService.sendMessage({
|
|||
|
|
message: message,
|
|||
|
|
conversationId: targetChatId, // 使用保存的targetChatId
|
|||
|
|
mode: isDeepSearchActive ? 'research' : 'chat'
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (response.success) {
|
|||
|
|
// 更新会话ID(如果是新会话)
|
|||
|
|
if (response.conversationId && !targetChatId) {
|
|||
|
|
const newChatId = response.conversationId;
|
|||
|
|
// 只有当前聊天与目标一致时才更新全局变量
|
|||
|
|
if (currentChatId === targetChatId) {
|
|||
|
|
currentChatId = newChatId;
|
|||
|
|
}
|
|||
|
|
if (window.chatManager) {
|
|||
|
|
window.chatManager.currentChatId = newChatId;
|
|||
|
|
}
|
|||
|
|
// 添加新会话到侧边栏
|
|||
|
|
const chatTitle = message.length > 20 ? message.substring(0, 20) + '...' : message;
|
|||
|
|
addChatToSidebar(newChatId, chatTitle, true);
|
|||
|
|
}
|
|||
|
|
// 使用addMessageToChat统一处理AI回复
|
|||
|
|
await addMessageToChat(response.answer, 'ai', targetChatId);
|
|||
|
|
} else {
|
|||
|
|
// 使用addMessageToChat统一处理错误消息
|
|||
|
|
await addMessageToChat('抱歉,获取回复时出现错误。', 'ai', targetChatId);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 恢复发送状态
|
|||
|
|
window.isSubmitting = false;
|
|||
|
|
updateChatModeSendUI(false);
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('发送消息失败:', error);
|
|||
|
|
// 使用addMessageToChat统一处理错误消息
|
|||
|
|
await addMessageToChat(`抱歉,发生了错误:${error.message}`, 'ai', targetChatId);
|
|||
|
|
window.isSubmitting = false;
|
|||
|
|
updateChatModeSendUI(false);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 重新聚焦输入框
|
|||
|
|
chatModeMessageInput.focus();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 聊天模式UI状态更新(直接调用统一的updateSendUI)
|
|||
|
|
function updateChatModeSendUI(isLoading) {
|
|||
|
|
updateSendUI(isLoading);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 暴露到全局
|
|||
|
|
window.updateChatModeSendUI = updateChatModeSendUI;
|
|||
|
|
|
|||
|
|
// 自动调整输入框高度(参考ChatGPT/DeepSeek)
|
|||
|
|
function autoResizeTextarea(textarea) {
|
|||
|
|
// 检查是否是欢迎模式的输入框(有特殊的最小高度)
|
|||
|
|
if (textarea.id === 'messageInput') {
|
|||
|
|
// 欢迎模式输入框保持固定高度124px,不自动调整
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 聊天模式输入框自动调整高度
|
|||
|
|
// 重置高度以获取正确的scrollHeight
|
|||
|
|
textarea.style.height = '40px';
|
|||
|
|
|
|||
|
|
// 计算新高度
|
|||
|
|
const newHeight = Math.min(textarea.scrollHeight, 120); // 最大120px
|
|||
|
|
textarea.style.height = newHeight + 'px';
|
|||
|
|
|
|||
|
|
// 如果内容超过最大高度,显示滚动条
|
|||
|
|
if (textarea.scrollHeight > 120) {
|
|||
|
|
textarea.style.overflowY = 'auto';
|
|||
|
|
} else {
|
|||
|
|
textarea.style.overflowY = 'hidden';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function sendMessage() {
|
|||
|
|
const message = messageInput.value.trim();
|
|||
|
|
if (!message || window.isSubmitting) {
|
|||
|
|
console.log('消息为空或正在发送中,不发送');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log('发送消息:', message);
|
|||
|
|
|
|||
|
|
// 设置发送状态
|
|||
|
|
window.isSubmitting = true;
|
|||
|
|
updateSendUI(true);
|
|||
|
|
|
|||
|
|
// 显示聊天容器
|
|||
|
|
showChatContainer();
|
|||
|
|
|
|||
|
|
// 如果是第一条消息且没有当前聊天ID,创建新聊天
|
|||
|
|
if (!currentChatId) {
|
|||
|
|
// 立即创建新的聊天会话
|
|||
|
|
if (window.chatManager) {
|
|||
|
|
currentChatId = window.chatManager.createChat(message);
|
|||
|
|
window.chatManager.currentChatId = currentChatId;
|
|||
|
|
|
|||
|
|
// 立即添加到侧边栏
|
|||
|
|
const chatTitle = message.length > 20 ? message.substring(0, 20) + '...' : message;
|
|||
|
|
addChatToSidebar(currentChatId, chatTitle, true);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 保存当前聊天ID,避免切换聊天记录时被改变
|
|||
|
|
const targetChatId = currentChatId;
|
|||
|
|
|
|||
|
|
// 添加用户消息(使用固定的chatId)
|
|||
|
|
await addMessageToChat(message, 'user', targetChatId);
|
|||
|
|
|
|||
|
|
// 清空输入框并重置高度
|
|||
|
|
messageInput.value = '';
|
|||
|
|
// 欢迎模式输入框保持124px高度
|
|||
|
|
if (messageInput.id === 'messageInput') {
|
|||
|
|
messageInput.style.height = '124px';
|
|||
|
|
} else {
|
|||
|
|
messageInput.style.height = '40px';
|
|||
|
|
}
|
|||
|
|
updateSendButtonState();
|
|||
|
|
|
|||
|
|
// 调用后端API获取AI回复
|
|||
|
|
try {
|
|||
|
|
// 判断是否使用流式响应
|
|||
|
|
const useStream = true; // 可以根据需要调整
|
|||
|
|
|
|||
|
|
if (useStream) {
|
|||
|
|
// 流式响应
|
|||
|
|
let aiMessageId = null;
|
|||
|
|
let aiContent = '';
|
|||
|
|
|
|||
|
|
// 获取工具设置
|
|||
|
|
const toolsSettings = window.getToolsSettings ? window.getToolsSettings() : { deepResearch: false, showThinking: false };
|
|||
|
|
const isDeepResearch = toolsSettings.deepResearch;
|
|||
|
|
const showThinking = toolsSettings.showThinking;
|
|||
|
|
|
|||
|
|
// 创建状态显示组件
|
|||
|
|
let statusDisplay = null;
|
|||
|
|
if (window.StreamStatusDisplay) {
|
|||
|
|
// 在消息区域之前创建状态显示容器
|
|||
|
|
const statusContainer = document.createElement('div');
|
|||
|
|
statusContainer.id = `status-${targetChatId}-${Date.now()}`;
|
|||
|
|
chatMessages.appendChild(statusContainer);
|
|||
|
|
statusDisplay = new window.StreamStatusDisplay(statusContainer);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const abortController = await window.ChatAPIService.sendMessageStream(
|
|||
|
|
window.currentAbortController = abortController; // 保存到全局以便中断
|
|||
|
|
{
|
|||
|
|
message: message,
|
|||
|
|
conversationId: targetChatId, // 使用保存的targetChatId
|
|||
|
|
mode: isDeepSearchActive ? 'research' : 'chat',
|
|||
|
|
deepResearch: isDeepResearch,
|
|||
|
|
showThinking: showThinking
|
|||
|
|
},
|
|||
|
|
// onMessage: 接收消息片段
|
|||
|
|
(content) => {
|
|||
|
|
// 只有当前聊天ID与目标ID一致时才更新UI
|
|||
|
|
if (currentChatId === targetChatId) {
|
|||
|
|
aiContent += content;
|
|||
|
|
if (aiMessageId) {
|
|||
|
|
// 更新已存在的消息
|
|||
|
|
updateMessageContent(aiMessageId, aiContent);
|
|||
|
|
} else {
|
|||
|
|
// 创建新消息
|
|||
|
|
aiMessageId = Date.now().toString();
|
|||
|
|
addStreamMessage(aiContent, 'ai', aiMessageId);
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
// 如果已切换到其他聊天,仅累积内容但不更新UI
|
|||
|
|
aiContent += content;
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
// onComplete: 完成回调
|
|||
|
|
async (data) => {
|
|||
|
|
console.log('流式响应完成:', data);
|
|||
|
|
// 使用保存的targetChatId保存AI回复到正确的聊天记录
|
|||
|
|
if (window.chatManager && targetChatId) {
|
|||
|
|
// 保存到目标聊天记录
|
|||
|
|
const originalChatId = window.chatManager.currentChatId;
|
|||
|
|
window.chatManager.currentChatId = targetChatId;
|
|||
|
|
window.chatManager.addMessage(aiContent, 'ai');
|
|||
|
|
// 如果当前聊天已切换,不需要恢复
|
|||
|
|
if (currentChatId !== targetChatId) {
|
|||
|
|
window.chatManager.currentChatId = originalChatId;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// 恢复发送状态
|
|||
|
|
window.isSubmitting = false;
|
|||
|
|
updateSendUI(false);
|
|||
|
|
},
|
|||
|
|
// onError: 错误回调
|
|||
|
|
(error) => {
|
|||
|
|
console.error('流式响应错误:', error);
|
|||
|
|
addMessage(`抱歉,发生了错误:${error.message}`, 'ai');
|
|||
|
|
window.isSubmitting = false;
|
|||
|
|
updateSendUI(false);
|
|||
|
|
},
|
|||
|
|
// onThinking: 思考过程回调
|
|||
|
|
showThinking ? (thinking) => {
|
|||
|
|
console.log('思考过程:', thinking);
|
|||
|
|
// 显示思考过程
|
|||
|
|
if (!document.getElementById('thinking-message')) {
|
|||
|
|
const thinkingDiv = document.createElement('div');
|
|||
|
|
thinkingDiv.id = 'thinking-message';
|
|||
|
|
thinkingDiv.className = 'thinking-process';
|
|||
|
|
thinkingDiv.innerHTML = `
|
|||
|
|
<div class="thinking-label">🤔 思考过程:</div>
|
|||
|
|
<div class="thinking-content"></div>
|
|||
|
|
`;
|
|||
|
|
chatMessages.appendChild(thinkingDiv);
|
|||
|
|
chatMessages.scrollTop = chatMessages.scrollHeight;
|
|||
|
|
}
|
|||
|
|
const thinkingContent = document.querySelector('#thinking-message .thinking-content');
|
|||
|
|
if (thinkingContent) {
|
|||
|
|
thinkingContent.textContent += thinking;
|
|||
|
|
}
|
|||
|
|
} : null,
|
|||
|
|
// onStatus: 状态更新回调(新增)
|
|||
|
|
(status) => {
|
|||
|
|
if (statusDisplay) {
|
|||
|
|
statusDisplay.updateStatus(status);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
} else {
|
|||
|
|
// 同步响应
|
|||
|
|
const response = await window.ChatAPIService.sendMessage({
|
|||
|
|
message: message,
|
|||
|
|
conversationId: targetChatId, // 使用保存的targetChatId
|
|||
|
|
mode: isDeepSearchActive ? 'research' : 'chat'
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (response.success) {
|
|||
|
|
// 更新会话ID(如果是新会话)
|
|||
|
|
if (response.conversationId && !targetChatId) {
|
|||
|
|
const newChatId = response.conversationId;
|
|||
|
|
// 只有当前聊天与目标一致时才更新全局变量
|
|||
|
|
if (currentChatId === targetChatId) {
|
|||
|
|
currentChatId = newChatId;
|
|||
|
|
}
|
|||
|
|
if (window.chatManager) {
|
|||
|
|
window.chatManager.currentChatId = newChatId;
|
|||
|
|
}
|
|||
|
|
// 添加新会话到侧边栏
|
|||
|
|
const chatTitle = message.length > 20 ? message.substring(0, 20) + '...' : message;
|
|||
|
|
addChatToSidebar(newChatId, chatTitle, true);
|
|||
|
|
}
|
|||
|
|
// 使用addMessageToChat统一处理AI回复
|
|||
|
|
await addMessageToChat(response.answer, 'ai', targetChatId);
|
|||
|
|
} else {
|
|||
|
|
// 使用addMessageToChat统一处理错误消息
|
|||
|
|
await addMessageToChat('抱歉,获取回复时出现错误。', 'ai', targetChatId);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 恢复发送状态
|
|||
|
|
window.isSubmitting = false;
|
|||
|
|
updateSendUI(false);
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('发送消息失败:', error);
|
|||
|
|
// 使用addMessageToChat统一处理错误消息
|
|||
|
|
await addMessageToChat(`抱歉,发生了错误:${error.message}`, 'ai', targetChatId);
|
|||
|
|
window.isSubmitting = false;
|
|||
|
|
updateSendUI(false);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 重新聚焦输入框
|
|||
|
|
messageInput.focus();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 新增: 添加消息到指定的聊天记录
|
|||
|
|
async function addMessageToChat(content, type, chatId) {
|
|||
|
|
// 确保聊天容器已显示
|
|||
|
|
showChatContainer();
|
|||
|
|
|
|||
|
|
// 使用新的聊天管理器
|
|||
|
|
if (window.chatManager && chatId) {
|
|||
|
|
// 只有登录用户才保存聊天记录
|
|||
|
|
if (window.chatManager.getCurrentUser()) {
|
|||
|
|
// 临时切换到目标聊天ID
|
|||
|
|
const originalChatId = window.chatManager.currentChatId;
|
|||
|
|
window.chatManager.currentChatId = chatId;
|
|||
|
|
const message = window.chatManager.addMessage(content, type);
|
|||
|
|
// 恢复原来的聊天ID
|
|||
|
|
window.chatManager.currentChatId = originalChatId;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果启用了在线模式,同步到服务器
|
|||
|
|
if (isOnlineMode && window.ChatAPIService) {
|
|||
|
|
try {
|
|||
|
|
await window.ChatAPIService.addMessage(chatId, {
|
|||
|
|
content,
|
|||
|
|
type,
|
|||
|
|
metadata: {}
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('同步消息到服务器失败:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
// 兼容旧的存储方式
|
|||
|
|
if (chatId && chatHistoryData.has(chatId)) {
|
|||
|
|
const chatHistory = chatHistoryData.get(chatId);
|
|||
|
|
chatHistory.push({
|
|||
|
|
content: content,
|
|||
|
|
type: type,
|
|||
|
|
timestamp: new Date().toISOString()
|
|||
|
|
});
|
|||
|
|
chatHistoryData.set(chatId, chatHistory);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 只有当前聊天ID与目标ID一致时才更新UI(不保存数据,避免重复)
|
|||
|
|
if (chatId === currentChatId) {
|
|||
|
|
await addMessageToUI(content, type);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 新增: 只更新UI的函数(不保存数据)
|
|||
|
|
async function addMessageToUI(content, type) {
|
|||
|
|
// 确保聊天容器已显示
|
|||
|
|
showChatContainer();
|
|||
|
|
|
|||
|
|
// 获取聊天消息容器
|
|||
|
|
const chatMessages = document.getElementById('chatMessages');
|
|||
|
|
if (!chatMessages) {
|
|||
|
|
console.error('聊天消息容器未找到');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const messageItem = document.createElement('div');
|
|||
|
|
messageItem.className = 'message-item mb-4';
|
|||
|
|
|
|||
|
|
const isUser = type === 'user';
|
|||
|
|
const currentTime = new Date().toLocaleTimeString('zh-CN', {
|
|||
|
|
hour: '2-digit',
|
|||
|
|
minute: '2-digit'
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 渲染内容(支持Markdown)
|
|||
|
|
let renderedContent = content;
|
|||
|
|
if (window.markdownRenderer) {
|
|||
|
|
renderedContent = window.markdownRenderer.render(content);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (isUser) {
|
|||
|
|
messageItem.innerHTML = `
|
|||
|
|
<div class="flex justify-end">
|
|||
|
|
<div class="message user-message px-4 py-3">
|
|||
|
|
<div class="text-sm text-gray-800 markdown-content">${renderedContent}</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="text-xs text-gray-500 text-right mt-1">${currentTime}</div>
|
|||
|
|
`;
|
|||
|
|
} else {
|
|||
|
|
messageItem.innerHTML = `
|
|||
|
|
<div class="flex justify-start">
|
|||
|
|
<div class="message ai-message px-4 py-3">
|
|||
|
|
<div class="text-sm text-gray-800 markdown-content">${renderedContent}</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="text-xs text-gray-500 mt-1">${currentTime}</div>
|
|||
|
|
`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
chatMessages.appendChild(messageItem);
|
|||
|
|
chatMessages.scrollTop = chatMessages.scrollHeight;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function addMessage(content, type) {
|
|||
|
|
// 确保聊天容器已显示
|
|||
|
|
showChatContainer();
|
|||
|
|
|
|||
|
|
// 使用新的聊天管理器
|
|||
|
|
if (window.chatManager && currentChatId) {
|
|||
|
|
// 只有登录用户才保存聊天记录
|
|||
|
|
if (window.chatManager.getCurrentUser()) {
|
|||
|
|
const message = window.chatManager.addMessage(content, type);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果启用了在线模式,同步到服务器
|
|||
|
|
if (isOnlineMode && window.ChatAPIService) {
|
|||
|
|
try {
|
|||
|
|
await window.ChatAPIService.addMessage(currentChatId, {
|
|||
|
|
content,
|
|||
|
|
type,
|
|||
|
|
metadata: {}
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('同步消息到服务器失败:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
// 兼容旧的存储方式
|
|||
|
|
if (currentChatId && chatHistoryData.has(currentChatId)) {
|
|||
|
|
const chatHistory = chatHistoryData.get(currentChatId);
|
|||
|
|
chatHistory.push({
|
|||
|
|
content: content,
|
|||
|
|
type: type,
|
|||
|
|
timestamp: new Date().toISOString()
|
|||
|
|
});
|
|||
|
|
chatHistoryData.set(currentChatId, chatHistory);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取聊天消息容器
|
|||
|
|
const chatMessages = document.getElementById('chatMessages');
|
|||
|
|
if (!chatMessages) {
|
|||
|
|
console.error('聊天消息容器未找到');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const messageItem = document.createElement('div');
|
|||
|
|
messageItem.className = 'message-item mb-4';
|
|||
|
|
|
|||
|
|
const isUser = type === 'user';
|
|||
|
|
const currentTime = new Date().toLocaleTimeString('zh-CN', {
|
|||
|
|
hour: '2-digit',
|
|||
|
|
minute: '2-digit'
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 渲染内容(支持Markdown)
|
|||
|
|
let renderedContent = content;
|
|||
|
|
if (window.markdownRenderer) {
|
|||
|
|
renderedContent = window.markdownRenderer.render(content);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (isUser) {
|
|||
|
|
messageItem.innerHTML = `
|
|||
|
|
<div class="flex justify-end">
|
|||
|
|
<div class="message user-message px-4 py-3">
|
|||
|
|
<div class="text-sm text-gray-800 markdown-content">${renderedContent}</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="text-xs text-gray-500 text-right mt-1">${currentTime}</div>
|
|||
|
|
`;
|
|||
|
|
} else {
|
|||
|
|
messageItem.innerHTML = `
|
|||
|
|
<div class="flex justify-start">
|
|||
|
|
<div class="message ai-message px-4 py-3">
|
|||
|
|
<div class="text-sm text-gray-800 markdown-content">${renderedContent}</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="text-xs text-gray-500 mt-1">${currentTime}</div>
|
|||
|
|
`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
chatMessages.appendChild(messageItem);
|
|||
|
|
chatMessages.scrollTop = chatMessages.scrollHeight;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 添加流式消息(用于实时更新)
|
|||
|
|
function addStreamMessage(content, type, messageId) {
|
|||
|
|
const chatMessages = document.getElementById('chatMessages');
|
|||
|
|
if (!chatMessages) {
|
|||
|
|
console.error('聊天消息容器未找到');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let messageItem = document.getElementById(`message-${messageId}`);
|
|||
|
|
|
|||
|
|
if (!messageItem) {
|
|||
|
|
// 创建新消息元素
|
|||
|
|
messageItem = document.createElement('div');
|
|||
|
|
messageItem.id = `message-${messageId}`;
|
|||
|
|
messageItem.className = 'message-item mb-4';
|
|||
|
|
chatMessages.appendChild(messageItem);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const currentTime = new Date().toLocaleTimeString('zh-CN', {
|
|||
|
|
hour: '2-digit',
|
|||
|
|
minute: '2-digit'
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 渲染内容(支持Markdown)
|
|||
|
|
let renderedContent = content;
|
|||
|
|
if (window.markdownRenderer) {
|
|||
|
|
renderedContent = window.markdownRenderer.render(content);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
messageItem.innerHTML = `
|
|||
|
|
<div class="flex justify-start">
|
|||
|
|
<div class="message ai-message px-4 py-3">
|
|||
|
|
<div class="text-sm text-gray-800 markdown-content">${renderedContent}</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="text-xs text-gray-500 mt-1">${currentTime}</div>
|
|||
|
|
`;
|
|||
|
|
|
|||
|
|
chatMessages.scrollTop = chatMessages.scrollHeight;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新消息内容(用于流式更新)
|
|||
|
|
function updateMessageContent(messageId, content) {
|
|||
|
|
const messageItem = document.getElementById(`message-${messageId}`);
|
|||
|
|
if (!messageItem) return;
|
|||
|
|
|
|||
|
|
const contentDiv = messageItem.querySelector('.markdown-content');
|
|||
|
|
if (!contentDiv) return;
|
|||
|
|
|
|||
|
|
// 渲染内容(支持Markdown)
|
|||
|
|
let renderedContent = content;
|
|||
|
|
if (window.markdownRenderer) {
|
|||
|
|
renderedContent = window.markdownRenderer.render(content);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
contentDiv.innerHTML = renderedContent;
|
|||
|
|
|
|||
|
|
// 保持滚动在底部
|
|||
|
|
const chatMessages = document.getElementById('chatMessages');
|
|||
|
|
if (chatMessages) {
|
|||
|
|
chatMessages.scrollTop = chatMessages.scrollHeight;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* ===== 深度检索功能 ===== */
|
|||
|
|
function toggleDeepSearch() {
|
|||
|
|
isDeepSearchActive = !isDeepSearchActive;
|
|||
|
|
const deepSearchToggle = document.getElementById('deepSearchToggle');
|
|||
|
|
|
|||
|
|
if (deepSearchToggle) {
|
|||
|
|
if (isDeepSearchActive) {
|
|||
|
|
deepSearchToggle.classList.add('active');
|
|||
|
|
deepSearchToggle.style.backgroundColor = '#dbeafe';
|
|||
|
|
deepSearchToggle.style.borderColor = '#3b82f6';
|
|||
|
|
deepSearchToggle.style.color = '#1d4ed8';
|
|||
|
|
} else {
|
|||
|
|
deepSearchToggle.classList.remove('active');
|
|||
|
|
deepSearchToggle.style.backgroundColor = '';
|
|||
|
|
deepSearchToggle.style.borderColor = '';
|
|||
|
|
deepSearchToggle.style.color = '';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log('深度检索状态:', isDeepSearchActive);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* ===== 用户下拉菜单 ===== */
|
|||
|
|
function initUserDropdown() {
|
|||
|
|
const userMenuToggle = document.getElementById('userMenuToggle');
|
|||
|
|
const userDropdown = document.getElementById('userDropdown');
|
|||
|
|
|
|||
|
|
if (!userMenuToggle || !userDropdown) return;
|
|||
|
|
|
|||
|
|
userMenuToggle.addEventListener('click', function(e) {
|
|||
|
|
e.stopPropagation();
|
|||
|
|
console.log('用户菜单被点击');
|
|||
|
|
toggleDropdown();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 点击其他地方关闭下拉菜单
|
|||
|
|
document.addEventListener('click', function(e) {
|
|||
|
|
if (!userDropdown.contains(e.target)) {
|
|||
|
|
closeDropdown();
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
function toggleDropdown() {
|
|||
|
|
if (userDropdown.classList.contains('hidden')) {
|
|||
|
|
userDropdown.classList.remove('hidden');
|
|||
|
|
console.log('打开用户下拉菜单');
|
|||
|
|
} else {
|
|||
|
|
userDropdown.classList.add('hidden');
|
|||
|
|
console.log('关闭用户下拉菜单');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function closeDropdown() {
|
|||
|
|
userDropdown.classList.add('hidden');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* ===== 下载弹窗功能 ===== */
|
|||
|
|
function initDownloadModal() {
|
|||
|
|
const downloadBtn = document.getElementById('downloadBtn');
|
|||
|
|
const downloadAppBtn = document.getElementById('downloadAppBtn');
|
|||
|
|
const downloadModal = document.getElementById('downloadModal');
|
|||
|
|
const closeDownloadModal = document.getElementById('closeDownloadModal');
|
|||
|
|
|
|||
|
|
if (!downloadModal || !closeDownloadModal) return;
|
|||
|
|
|
|||
|
|
// 折叠栏下载按钮点击事件
|
|||
|
|
if (downloadBtn) {
|
|||
|
|
downloadBtn.addEventListener('click', function(e) {
|
|||
|
|
e.preventDefault();
|
|||
|
|
downloadModal.classList.remove('hidden');
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 侧边栏下载APP按钮点击事件
|
|||
|
|
if (downloadAppBtn) {
|
|||
|
|
downloadAppBtn.addEventListener('click', function(e) {
|
|||
|
|
e.preventDefault();
|
|||
|
|
downloadModal.classList.remove('hidden');
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 关闭下载弹窗
|
|||
|
|
closeDownloadModal.addEventListener('click', function(e) {
|
|||
|
|
e.preventDefault();
|
|||
|
|
downloadModal.classList.add('hidden');
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 点击遮罩层关闭弹窗
|
|||
|
|
downloadModal.addEventListener('click', function(e) {
|
|||
|
|
if (e.target === downloadModal) {
|
|||
|
|
downloadModal.classList.add('hidden');
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* ===== 搜索功能 ===== */
|
|||
|
|
function initSearch() {
|
|||
|
|
const searchInput = document.getElementById('searchInput');
|
|||
|
|
|
|||
|
|
if (!searchInput) return;
|
|||
|
|
|
|||
|
|
searchInput.addEventListener('input', function() {
|
|||
|
|
const searchTerm = this.value.toLowerCase().trim();
|
|||
|
|
const chatItems = document.querySelectorAll('.chat-item');
|
|||
|
|
|
|||
|
|
chatItems.forEach(item => {
|
|||
|
|
const text = item.textContent.toLowerCase();
|
|||
|
|
if (text.includes(searchTerm) || searchTerm === '') {
|
|||
|
|
item.style.display = '';
|
|||
|
|
} else {
|
|||
|
|
item.style.display = 'none';
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
console.log('搜索:', searchTerm);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* ===== 聊天历史功能 ===== */
|
|||
|
|
function initChatHistoryActions() {
|
|||
|
|
const chatItems = document.querySelectorAll('.chat-item');
|
|||
|
|
|
|||
|
|
chatItems.forEach(item => {
|
|||
|
|
// 点击聊天项
|
|||
|
|
item.addEventListener('click', function(e) {
|
|||
|
|
if (!e.target.classList.contains('delete-chat')) {
|
|||
|
|
selectChatItem(this);
|
|||
|
|
console.log('选择聊天记录:', this.textContent.trim());
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 删除按钮
|
|||
|
|
const deleteBtn = item.querySelector('.delete-chat');
|
|||
|
|
if (deleteBtn) {
|
|||
|
|
deleteBtn.addEventListener('click', function(e) {
|
|||
|
|
e.stopPropagation();
|
|||
|
|
deleteChatSession(item);
|
|||
|
|
console.log('删除聊天记录');
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function selectChatItem(item) {
|
|||
|
|
// 移除其他active状态
|
|||
|
|
document.querySelectorAll('.chat-history-item').forEach(chatItem => {
|
|||
|
|
chatItem.classList.remove('active');
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 添加active状态
|
|||
|
|
item.classList.add('active');
|
|||
|
|
|
|||
|
|
// 获取聊天ID
|
|||
|
|
const chatId = item.getAttribute('data-chat-id');
|
|||
|
|
|
|||
|
|
// 显示聊天容器
|
|||
|
|
showChatContainer();
|
|||
|
|
|
|||
|
|
// 加载对话内容
|
|||
|
|
if (chatId) {
|
|||
|
|
loadChatHistory(chatId);
|
|||
|
|
console.log('加载聊天历史:', chatId);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function deleteChatSession(item) {
|
|||
|
|
if (confirm('确定要删除这个对话吗?')) {
|
|||
|
|
item.remove();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* ===== 新建对话 ===== */
|
|||
|
|
function startNewChat() {
|
|||
|
|
// 清除active状态
|
|||
|
|
document.querySelectorAll('.chat-history-item').forEach(item => {
|
|||
|
|
item.classList.remove('active');
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 移除聊天模式类
|
|||
|
|
document.body.classList.remove('chat-mode');
|
|||
|
|
|
|||
|
|
// 切换回欢迎模式
|
|||
|
|
const chatContainer = document.getElementById('chatContainer');
|
|||
|
|
const welcomeMode = document.getElementById('welcomeMode');
|
|||
|
|
const chatModeInput = document.getElementById('chatModeInput');
|
|||
|
|
const tickerSection = document.querySelector('.ticker-section'); // 3D新闻卡片区域
|
|||
|
|
|
|||
|
|
if (chatContainer) {
|
|||
|
|
chatContainer.style.display = 'none';
|
|||
|
|
|
|||
|
|
// 恢复动画和轮播
|
|||
|
|
if (window.chatModeOptimizer) {
|
|||
|
|
window.chatModeOptimizer.resume();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (welcomeMode) {
|
|||
|
|
welcomeMode.style.display = 'flex'; // 使用flex保持居中布局
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (chatModeInput) {
|
|||
|
|
chatModeInput.classList.add('hidden');
|
|||
|
|
chatModeInput.style.display = 'none'; // 确保完全隐藏
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 显示3D新闻卡片区域
|
|||
|
|
if (tickerSection) {
|
|||
|
|
tickerSection.style.display = 'block';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 重置当前聊天ID
|
|||
|
|
currentChatId = null;
|
|||
|
|
|
|||
|
|
// 使用聊天管理器开始新聊天
|
|||
|
|
if (window.chatManager) {
|
|||
|
|
window.chatManager.startNewChat();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 聚焦输入框
|
|||
|
|
const messageInput = document.getElementById('messageInput');
|
|||
|
|
if (messageInput) {
|
|||
|
|
messageInput.focus();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果当前正在提交消息,保持等待状态
|
|||
|
|
if (window.isSubmitting) {
|
|||
|
|
updateSendUI(true);
|
|||
|
|
console.log('返回首页,保持等待状态');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log('开始新对话,恢复欢迎界面和3D图片');
|
|||
|
|
|
|||
|
|
// 重新初始化文字轮播(延迟执行确保DOM已更新)
|
|||
|
|
setTimeout(() => {
|
|||
|
|
console.log('重新初始化文字轮播...');
|
|||
|
|
initTextRotator();
|
|||
|
|
}, 300);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* ===== 辅助函数 ===== */
|
|||
|
|
function showChatContainer() {
|
|||
|
|
const chatContainer = document.getElementById('chatContainer');
|
|||
|
|
const welcomeMode = document.getElementById('welcomeMode');
|
|||
|
|
const chatModeInput = document.getElementById('chatModeInput');
|
|||
|
|
const chatMessages = document.getElementById('chatMessages');
|
|||
|
|
const bottomFunctionArea = document.querySelector('.flex-shrink-0'); // 底部功能区
|
|||
|
|
const tickerSection = document.querySelector('.ticker-section'); // 3D新闻卡片区域
|
|||
|
|
|
|||
|
|
if (chatContainer && welcomeMode && chatModeInput) {
|
|||
|
|
// 添加聊天模式类到body
|
|||
|
|
document.body.classList.add('chat-mode');
|
|||
|
|
|
|||
|
|
// 隐藏欢迎模式
|
|||
|
|
welcomeMode.style.display = 'none';
|
|||
|
|
|
|||
|
|
// 隐藏底部功能区
|
|||
|
|
if (bottomFunctionArea) {
|
|||
|
|
bottomFunctionArea.style.display = 'none';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 隐藏3D新闻卡片区域
|
|||
|
|
if (tickerSection) {
|
|||
|
|
tickerSection.style.display = 'none';
|
|||
|
|
console.log('showChatContainer: 隐藏3D图片');
|
|||
|
|
} else {
|
|||
|
|
console.log('showChatContainer: 未找到ticker-section');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 显示聊天容器(全屏)
|
|||
|
|
chatContainer.style.display = 'block';
|
|||
|
|
|
|||
|
|
// 优化聊天模式性能 - 暂停动画和轮播
|
|||
|
|
if (window.chatModeOptimizer) {
|
|||
|
|
window.chatModeOptimizer.pause();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 确保聊天消息容器可见
|
|||
|
|
if (chatMessages) {
|
|||
|
|
chatMessages.classList.remove('hidden');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 显示底部固定输入框
|
|||
|
|
chatModeInput.classList.remove('hidden');
|
|||
|
|
chatModeInput.style.display = 'block'; // 确保显示
|
|||
|
|
|
|||
|
|
// 如果当前正在提交消息,确保聊天模式也显示等待状态
|
|||
|
|
if (window.isSubmitting) {
|
|||
|
|
updateSendUI(true);
|
|||
|
|
console.log('切换到聊天模式,保持等待状态');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log('切换到聊天模式:隐藏欢迎区、3D图片和底部功能区,输入框下沉到底部,聊天窗口全屏显示');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function createNewChatSession(firstMessage) {
|
|||
|
|
// 使用后端会话管理,不再创建本地会话ID
|
|||
|
|
// 会话ID将在第一次发送消息后由后端返回
|
|||
|
|
console.log('准备创建新会话,等待后端返回会话ID');
|
|||
|
|
|
|||
|
|
// 清空当前会话ID,让后端创建新会话
|
|||
|
|
currentChatId = null;
|
|||
|
|
|
|||
|
|
// 如果使用本地聊天管理器(离线模式)
|
|||
|
|
if (window.chatManager && !window.ChatAPIService) {
|
|||
|
|
const chatId = window.chatManager.createChat(firstMessage);
|
|||
|
|
const chat = window.chatManager.getChat(chatId);
|
|||
|
|
const chatTitle = chat ? chat.title : '新对话';
|
|||
|
|
|
|||
|
|
// 添加到侧边栏
|
|||
|
|
addChatToSidebar(chatId, chatTitle);
|
|||
|
|
|
|||
|
|
return chatId;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 使用后端API时,返回null,会话ID将在第一次消息响应时获得
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function addChatToSidebar(chatId, title, shouldSelect = false) {
|
|||
|
|
const chatHistory = document.querySelector('.chat-history-list');
|
|||
|
|
if (!chatHistory) return;
|
|||
|
|
|
|||
|
|
// 检查会话是否已经存在于侧边栏
|
|||
|
|
const existingItem = chatHistory.querySelector(`[data-chat-id="${chatId}"]`);
|
|||
|
|
if (existingItem) {
|
|||
|
|
console.log(`会话 ${chatId} 已存在于侧边栏,跳过添加`);
|
|||
|
|
// 如果需要选中,直接选中现有项
|
|||
|
|
if (shouldSelect) {
|
|||
|
|
existingItem.click();
|
|||
|
|
}
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 创建新的聊天项
|
|||
|
|
const chatItem = document.createElement('div');
|
|||
|
|
chatItem.className = 'chat-history-item';
|
|||
|
|
chatItem.setAttribute('data-chat-id', chatId);
|
|||
|
|
|
|||
|
|
chatItem.innerHTML = `
|
|||
|
|
<div class="chat-title">${title}</div>
|
|||
|
|
<button class="delete-chat" style="display: none; position: absolute; right: 10px; top: 50%; transform: translateY(-50%); background: none; border: none; color: #999; cursor: pointer;">
|
|||
|
|
<i class="fas fa-trash"></i>
|
|||
|
|
</button>
|
|||
|
|
`;
|
|||
|
|
|
|||
|
|
// 添加鼠标悬停显示删除按钮
|
|||
|
|
chatItem.addEventListener('mouseenter', function() {
|
|||
|
|
const deleteBtn = this.querySelector('.delete-chat');
|
|||
|
|
if (deleteBtn) deleteBtn.style.display = 'block';
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
chatItem.addEventListener('mouseleave', function() {
|
|||
|
|
const deleteBtn = this.querySelector('.delete-chat');
|
|||
|
|
if (deleteBtn) deleteBtn.style.display = 'none';
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 添加事件监听器
|
|||
|
|
chatItem.addEventListener('click', function(e) {
|
|||
|
|
if (!e.target.classList.contains('delete-chat') && !e.target.closest('.delete-chat')) {
|
|||
|
|
selectChatItem(this);
|
|||
|
|
console.log('选择聊天记录:', title);
|
|||
|
|
|
|||
|
|
// 切换到选中的聊天
|
|||
|
|
if (window.chatManager) {
|
|||
|
|
const switched = window.chatManager.switchToChat(chatId);
|
|||
|
|
console.log('切换聊天结果:', switched);
|
|||
|
|
if (!switched) {
|
|||
|
|
console.error('聊天切换失败,聊天ID不存在:', chatId);
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
// 兼容旧方式
|
|||
|
|
loadChatHistory(chatId);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const deleteBtn = chatItem.querySelector('.delete-chat');
|
|||
|
|
if (deleteBtn) {
|
|||
|
|
deleteBtn.addEventListener('click', function(e) {
|
|||
|
|
e.stopPropagation();
|
|||
|
|
|
|||
|
|
// 使用新的聊天管理器删除
|
|||
|
|
if (window.chatManager) {
|
|||
|
|
const confirmDelete = confirm('确定要删除这个聊天记录吗?');
|
|||
|
|
if (confirmDelete) {
|
|||
|
|
window.chatManager.deleteChat(chatId);
|
|||
|
|
console.log('删除聊天记录:', chatId);
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
// 兼容旧方式
|
|||
|
|
deleteChatSession(chatItem);
|
|||
|
|
console.log('删除聊天记录');
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 将新聊天添加到"今天"分组的第一项
|
|||
|
|
const todaySection = chatHistory;
|
|||
|
|
todaySection.insertBefore(chatItem, todaySection.firstChild);
|
|||
|
|
|
|||
|
|
// 只在需要时选中聊天(比如新创建时)
|
|||
|
|
if (shouldSelect) {
|
|||
|
|
selectChatItem(chatItem);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function generateAIResponse(message) {
|
|||
|
|
// 根据用户输入生成包含Markdown格式的响应
|
|||
|
|
const markdownResponses = [
|
|||
|
|
`感谢您的提问!我正在为您分析这个问题。
|
|||
|
|
|
|||
|
|
## 主要观点
|
|||
|
|
|
|||
|
|
1. **第一点**:这是一个重要的考虑因素
|
|||
|
|
2. **第二点**:需要注意相关的细节
|
|||
|
|
3. **第三点**:可以从多个角度来理解
|
|||
|
|
|
|||
|
|
### 补充说明
|
|||
|
|
> 这是一段引用,包含了一些额外的信息。
|
|||
|
|
|
|||
|
|
您还有其他问题吗?`,
|
|||
|
|
|
|||
|
|
`这是一个很有趣的话题,让我为您详细解答。
|
|||
|
|
|
|||
|
|
### 关键概念
|
|||
|
|
- **概念A**:基础定义和说明
|
|||
|
|
- **概念B**:深入理解的要点
|
|||
|
|
- **概念C**:实际应用场景
|
|||
|
|
|
|||
|
|
\`\`\`javascript
|
|||
|
|
// 示例代码
|
|||
|
|
function example() {
|
|||
|
|
console.log("Hello, World!");
|
|||
|
|
}
|
|||
|
|
\`\`\`
|
|||
|
|
|
|||
|
|
希望这些信息对您有所帮助!`,
|
|||
|
|
|
|||
|
|
`根据您的问题,我建议从以下几个方面来考虑:
|
|||
|
|
|
|||
|
|
1. **技术层面**
|
|||
|
|
- 考虑点1
|
|||
|
|
- 考虑点2
|
|||
|
|
|
|||
|
|
2. **实践层面**
|
|||
|
|
- 实施步骤A
|
|||
|
|
- 实施步骤B
|
|||
|
|
|
|||
|
|
### 相关资源
|
|||
|
|
- [参考链接1](https://example.com)
|
|||
|
|
- [参考链接2](https://example.com)
|
|||
|
|
|
|||
|
|
*注意*:请根据实际情况调整方案。`,
|
|||
|
|
|
|||
|
|
`我理解您的需求,这里有一些相关的信息可能对您有帮助。
|
|||
|
|
|
|||
|
|
## 解决方案
|
|||
|
|
|
|||
|
|
### 方案一:快速实现
|
|||
|
|
\`\`\`bash
|
|||
|
|
# 命令行示例
|
|||
|
|
npm install package-name
|
|||
|
|
npm run start
|
|||
|
|
\`\`\`
|
|||
|
|
|
|||
|
|
### 方案二:完整配置
|
|||
|
|
1. 首先,创建配置文件
|
|||
|
|
2. 然后,设置必要的参数
|
|||
|
|
3. 最后,运行测试验证
|
|||
|
|
|
|||
|
|
**提示**:记得检查兼容性问题。`,
|
|||
|
|
|
|||
|
|
`让我为您提供一些相关的建议和解决方案。
|
|||
|
|
|
|||
|
|
## 核心要点
|
|||
|
|
|
|||
|
|
| 特性 | 描述 | 优先级 |
|
|||
|
|
|------|------|--------|
|
|||
|
|
| 性能 | 优化响应速度 | 高 |
|
|||
|
|
| 安全 | 加强数据保护 | 高 |
|
|||
|
|
| 易用 | 改善用户体验 | 中 |
|
|||
|
|
|
|||
|
|
### 实施建议
|
|||
|
|
> 💡 **最佳实践**:始终遵循行业标准和规范。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
如需更多帮助,请随时告诉我!`
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
// 检查用户消息是否包含代码相关关键词
|
|||
|
|
const codeKeywords = ['代码', 'code', '函数', 'function', '编程', 'programming', 'bug', '错误', 'error'];
|
|||
|
|
const hasCodeContext = codeKeywords.some(keyword => message.toLowerCase().includes(keyword));
|
|||
|
|
|
|||
|
|
if (hasCodeContext) {
|
|||
|
|
// 如果是代码相关问题,返回包含代码块的响应
|
|||
|
|
return markdownResponses[1]; // 返回包含代码示例的响应
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 随机返回一个Markdown格式的响应
|
|||
|
|
return markdownResponses[Math.floor(Math.random() * markdownResponses.length)];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 删除旧的loadChatHistory函数,使用后面定义的新版本
|
|||
|
|
|
|||
|
|
/* ===== 文字轮播技术 ===== */
|
|||
|
|
function initTextRotator() {
|
|||
|
|
// 文字轮播功能
|
|||
|
|
class TextRotator {
|
|||
|
|
constructor(options) {
|
|||
|
|
this.element = document.querySelector(options.selector);
|
|||
|
|
this.words = options.words || ['资料', '专家', '活动', '标准', '合作'];
|
|||
|
|
this.interval = options.interval || 2500;
|
|||
|
|
this.currentIndex = 0;
|
|||
|
|
this.isAnimating = false;
|
|||
|
|
|
|||
|
|
if (this.element) {
|
|||
|
|
this.init();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
init() {
|
|||
|
|
// 开始时显示第一个词
|
|||
|
|
this.element.textContent = this.words[0];
|
|||
|
|
console.log('文字轮播初始化:', this.words[0]);
|
|||
|
|
|
|||
|
|
// 等待一段时间后开始轮播
|
|||
|
|
setTimeout(() => {
|
|||
|
|
this.startRotation();
|
|||
|
|
console.log('开始文字轮播');
|
|||
|
|
}, 1000);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
startRotation() {
|
|||
|
|
setInterval(() => {
|
|||
|
|
this.rotateText();
|
|||
|
|
}, this.interval);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
rotateText() {
|
|||
|
|
if (this.isAnimating) return;
|
|||
|
|
|
|||
|
|
this.isAnimating = true;
|
|||
|
|
const nextWord = this.words[(this.currentIndex + 1) % this.words.length];
|
|||
|
|
console.log('轮播切换到:', nextWord);
|
|||
|
|
|
|||
|
|
// 同时获取倒影元素
|
|||
|
|
const reflectionElement = document.querySelector('#rotating-word-reflection');
|
|||
|
|
|
|||
|
|
// 退出动画
|
|||
|
|
this.element.style.transition = 'all 0.2s ease-out';
|
|||
|
|
this.element.style.opacity = '0';
|
|||
|
|
this.element.style.transform = 'translateY(-10px) scale(1.02)';
|
|||
|
|
|
|||
|
|
if (reflectionElement) {
|
|||
|
|
reflectionElement.style.transition = 'all 0.2s ease-out';
|
|||
|
|
reflectionElement.style.opacity = '0';
|
|||
|
|
reflectionElement.style.transform = 'translateY(10px) scale(1.02)';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
setTimeout(() => {
|
|||
|
|
// 更换文字
|
|||
|
|
this.currentIndex = (this.currentIndex + 1) % this.words.length;
|
|||
|
|
this.element.textContent = this.words[this.currentIndex];
|
|||
|
|
if (reflectionElement) {
|
|||
|
|
reflectionElement.textContent = this.words[this.currentIndex];
|
|||
|
|
}
|
|||
|
|
console.log('文字已更换为:', this.words[this.currentIndex]);
|
|||
|
|
|
|||
|
|
// 重置位置准备进入动画
|
|||
|
|
this.element.style.transition = 'none';
|
|||
|
|
this.element.style.transform = 'translateY(10px) scale(0.98)';
|
|||
|
|
this.element.style.opacity = '0';
|
|||
|
|
|
|||
|
|
if (reflectionElement) {
|
|||
|
|
reflectionElement.style.transition = 'none';
|
|||
|
|
reflectionElement.style.transform = 'translateY(-10px) scale(0.98)';
|
|||
|
|
reflectionElement.style.opacity = '0';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 强制重绘
|
|||
|
|
requestAnimationFrame(() => {
|
|||
|
|
// 进入动画
|
|||
|
|
this.element.style.transition = 'all 0.3s ease-out';
|
|||
|
|
this.element.style.opacity = '1';
|
|||
|
|
this.element.style.transform = 'translateY(0) scale(1)';
|
|||
|
|
|
|||
|
|
if (reflectionElement) {
|
|||
|
|
reflectionElement.style.transition = 'all 0.3s ease-out';
|
|||
|
|
reflectionElement.style.opacity = '0.7';
|
|||
|
|
reflectionElement.style.transform = 'translateY(0) scale(1)';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
setTimeout(() => {
|
|||
|
|
this.isAnimating = false;
|
|||
|
|
console.log('动画完成');
|
|||
|
|
}, 300);
|
|||
|
|
});
|
|||
|
|
}, 200);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 初始化文字轮播
|
|||
|
|
const rotatingElement = document.querySelector('#rotating-word');
|
|||
|
|
console.log('轮播元素查找结果:', rotatingElement);
|
|||
|
|
|
|||
|
|
if (rotatingElement) {
|
|||
|
|
const textRotator = new TextRotator({
|
|||
|
|
selector: '#rotating-word',
|
|||
|
|
words: ['资料', '专家', '活动', '标准', '合作'],
|
|||
|
|
interval: 2500
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 添加点击跳转功能和波纹效果
|
|||
|
|
rotatingElement.addEventListener('click', function(e) {
|
|||
|
|
console.log('点击轮播文字:', this.textContent);
|
|||
|
|
|
|||
|
|
// 创建波纹效果
|
|||
|
|
const ripple = document.createElement('div');
|
|||
|
|
const rect = this.getBoundingClientRect();
|
|||
|
|
const size = Math.max(rect.width, rect.height);
|
|||
|
|
const x = e.clientX - rect.left - size / 2;
|
|||
|
|
const y = e.clientY - rect.top - size / 2;
|
|||
|
|
|
|||
|
|
ripple.style.cssText = `
|
|||
|
|
position: absolute;
|
|||
|
|
width: ${size}px;
|
|||
|
|
height: ${size}px;
|
|||
|
|
left: ${x}px;
|
|||
|
|
top: ${y}px;
|
|||
|
|
background: radial-gradient(circle, rgba(30, 64, 175, 0.3) 0%, transparent 70%);
|
|||
|
|
border-radius: 50%;
|
|||
|
|
transform: scale(0);
|
|||
|
|
animation: ripple 0.6s ease-out;
|
|||
|
|
pointer-events: none;
|
|||
|
|
z-index: 1;
|
|||
|
|
`;
|
|||
|
|
|
|||
|
|
this.style.position = 'relative';
|
|||
|
|
this.appendChild(ripple);
|
|||
|
|
|
|||
|
|
setTimeout(() => {
|
|||
|
|
ripple.remove();
|
|||
|
|
}, 600);
|
|||
|
|
|
|||
|
|
// 跳转到新窗口
|
|||
|
|
window.open('https://yundage.com/home', '_blank');
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 3D鼠标跟随效果已移除,保持静态显示
|
|||
|
|
} else {
|
|||
|
|
console.error('未找到轮播元素 #rotating-word');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* ===== 鼠标跟随翻转卡片新闻跑马灯功能 ===== */
|
|||
|
|
function init3DTicker() {
|
|||
|
|
const ticker3D = document.getElementById('ticker3D');
|
|||
|
|
const tickerCarousel = document.getElementById('tickerCarousel');
|
|||
|
|
const tickerParticles = document.getElementById('tickerParticles');
|
|||
|
|
|
|||
|
|
if (!ticker3D || !tickerCarousel || !tickerParticles) {
|
|||
|
|
console.log('翻转卡片跑马灯元素未找到');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log('初始化鼠标跟随翻转卡片新闻跑马灯');
|
|||
|
|
|
|||
|
|
let newsIndex = 0;
|
|||
|
|
|
|||
|
|
// 新闻图片数据,对应images目录下的新闻1-10.png
|
|||
|
|
const newsData = [
|
|||
|
|
{ image: '新闻1.png', alt: '新闻1' },
|
|||
|
|
{ image: '新闻2.png', alt: '新闻2' },
|
|||
|
|
{ image: '新闻3.png', alt: '新闻3' },
|
|||
|
|
{ image: '新闻4.png', alt: '新闻4' },
|
|||
|
|
{ image: '新闻5.png', alt: '新闻5' },
|
|||
|
|
{ image: '新闻6.png', alt: '新闻6' },
|
|||
|
|
{ image: '新闻7.png', alt: '新闻7' },
|
|||
|
|
{ image: '新闻8.png', alt: '新闻8' },
|
|||
|
|
{ image: '新闻9.png', alt: '新闻9' },
|
|||
|
|
{ image: '新闻10.png', alt: '新闻10' }
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
// 创建背景粒子
|
|||
|
|
function createTickerParticles() {
|
|||
|
|
tickerParticles.innerHTML = '';
|
|||
|
|
|
|||
|
|
for (let i = 0; i < 8; i++) {
|
|||
|
|
const particle = document.createElement('div');
|
|||
|
|
particle.className = 'ticker-particle';
|
|||
|
|
particle.style.left = Math.random() * 100 + '%';
|
|||
|
|
particle.style.animationDelay = Math.random() * 8 + 's';
|
|||
|
|
particle.style.animationDuration = (Math.random() * 4 + 6) + 's';
|
|||
|
|
tickerParticles.appendChild(particle);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 双向鼠标跟随翻转效果
|
|||
|
|
function initMouseFollowFlip() {
|
|||
|
|
const cards = tickerCarousel.querySelectorAll('.ticker-flip-card');
|
|||
|
|
let lastMouseX = 0;
|
|||
|
|
let lastDirection = 0; // -1左,1右,0静止
|
|||
|
|
|
|||
|
|
// 鼠标移动事件
|
|||
|
|
ticker3D.addEventListener('mousemove', function(e) {
|
|||
|
|
const rect = ticker3D.getBoundingClientRect();
|
|||
|
|
const mouseX = e.clientX - rect.left;
|
|||
|
|
|
|||
|
|
// 计算鼠标移动方向
|
|||
|
|
const direction = mouseX > lastMouseX ? 1 : mouseX < lastMouseX ? -1 : 0;
|
|||
|
|
if (direction !== 0) {
|
|||
|
|
lastDirection = direction;
|
|||
|
|
}
|
|||
|
|
lastMouseX = mouseX;
|
|||
|
|
|
|||
|
|
// 为每个卡片计算翻转角度
|
|||
|
|
cards.forEach((card, index) => {
|
|||
|
|
const cardRect = card.getBoundingClientRect();
|
|||
|
|
const cardLeft = cardRect.left - rect.left;
|
|||
|
|
const cardRight = cardLeft + cardRect.width;
|
|||
|
|
const cardWidth = cardRect.width;
|
|||
|
|
const cardCenter = cardLeft + cardWidth / 2;
|
|||
|
|
|
|||
|
|
// 计算鼠标到卡片中心的距离
|
|||
|
|
const distanceToCenter = Math.abs(mouseX - cardCenter);
|
|||
|
|
const maxDistance = cardWidth * 2; // 影响范围扩大到卡片宽度的2倍
|
|||
|
|
|
|||
|
|
// 基础翻转进度(基于鼠标在卡片中的位置)
|
|||
|
|
let baseProgress = 0;
|
|||
|
|
|
|||
|
|
if (mouseX >= cardLeft && mouseX <= cardRight) {
|
|||
|
|
// 鼠标在卡片范围内
|
|||
|
|
baseProgress = (mouseX - cardLeft) / cardWidth;
|
|||
|
|
} else if (mouseX < cardLeft) {
|
|||
|
|
// 鼠标在卡片左侧
|
|||
|
|
const distanceFromLeft = cardLeft - mouseX;
|
|||
|
|
baseProgress = Math.max(0, 1 - (distanceFromLeft / cardWidth));
|
|||
|
|
} else {
|
|||
|
|
// 鼠标在卡片右侧
|
|||
|
|
const distanceFromRight = mouseX - cardRight;
|
|||
|
|
baseProgress = Math.max(0, 1 - (distanceFromRight / cardWidth));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 邻近卡片的影响(双向)
|
|||
|
|
let neighborInfluence = 0;
|
|||
|
|
|
|||
|
|
// 检查相邻卡片的影响
|
|||
|
|
for (let i = 0; i < cards.length; i++) {
|
|||
|
|
if (i === index) continue;
|
|||
|
|
|
|||
|
|
const neighborCard = cards[i];
|
|||
|
|
const neighborRect = neighborCard.getBoundingClientRect();
|
|||
|
|
const neighborLeft = neighborRect.left - rect.left;
|
|||
|
|
const neighborRight = neighborLeft + neighborRect.width;
|
|||
|
|
const neighborCenter = neighborLeft + neighborRect.width / 2;
|
|||
|
|
|
|||
|
|
// 如果鼠标在相邻卡片上,计算对当前卡片的影响
|
|||
|
|
if (mouseX >= neighborLeft && mouseX <= neighborRight) {
|
|||
|
|
const neighborProgress = (mouseX - neighborLeft) / neighborRect.width;
|
|||
|
|
const distance = Math.abs(index - i);
|
|||
|
|
const influence = neighborProgress * Math.pow(0.6, distance); // 距离越远影响越小
|
|||
|
|
|
|||
|
|
// 根据方向确定影响方式
|
|||
|
|
if ((i < index && lastDirection === 1) || (i > index && lastDirection === -1)) {
|
|||
|
|
neighborInfluence = Math.max(neighborInfluence, influence);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 计算最终翻转进度
|
|||
|
|
const finalProgress = Math.min(1, Math.max(0, baseProgress + neighborInfluence));
|
|||
|
|
|
|||
|
|
// 根据鼠标方向和位置决定翻转角度
|
|||
|
|
let rotationAngle = 0;
|
|||
|
|
|
|||
|
|
if (finalProgress > 0) {
|
|||
|
|
// 基础翻转角度
|
|||
|
|
rotationAngle = finalProgress * 180;
|
|||
|
|
|
|||
|
|
// 考虑方向性:从右往左时可能需要反向翻转
|
|||
|
|
if (lastDirection === -1 && mouseX < cardCenter) {
|
|||
|
|
// 从右往左且鼠标在卡片左侧时,使用反向翻转
|
|||
|
|
rotationAngle = (1 - finalProgress) * 180;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 应用真正的3D翻转效果
|
|||
|
|
const inner = card.querySelector('.flip-card-inner');
|
|||
|
|
if (inner) {
|
|||
|
|
inner.style.transform = `rotateY(${rotationAngle}deg)`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 调试信息(只显示第一张卡片)
|
|||
|
|
if (index === 0) {
|
|||
|
|
console.log(`Card ${index}: mouseX=${mouseX.toFixed(0)}, direction=${lastDirection}, baseProgress=${baseProgress.toFixed(2)}, neighborInfluence=${neighborInfluence.toFixed(2)}, rotation=${rotationAngle.toFixed(1)}°`);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 鼠标离开时重置
|
|||
|
|
ticker3D.addEventListener('mouseleave', function() {
|
|||
|
|
cards.forEach(card => {
|
|||
|
|
const inner = card.querySelector('.flip-card-inner');
|
|||
|
|
if (inner) {
|
|||
|
|
inner.style.transform = 'rotateY(0deg)';
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
lastMouseX = 0;
|
|||
|
|
lastDirection = 0;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 鼠标进入时记录初始位置
|
|||
|
|
ticker3D.addEventListener('mouseenter', function(e) {
|
|||
|
|
const rect = ticker3D.getBoundingClientRect();
|
|||
|
|
lastMouseX = e.clientX - rect.left;
|
|||
|
|
lastDirection = 0;
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 新闻轮播功能 - 更新卡片内容
|
|||
|
|
function rotateNews() {
|
|||
|
|
const cards = tickerCarousel.querySelectorAll('.ticker-flip-card');
|
|||
|
|
|
|||
|
|
cards.forEach((card, index) => {
|
|||
|
|
const currentNewsIndex = (newsIndex + index) % newsData.length;
|
|||
|
|
const newsItem = newsData[currentNewsIndex];
|
|||
|
|
|
|||
|
|
// 更新正面图片
|
|||
|
|
const frontImage = card.querySelector('.flip-card-front .news-image');
|
|||
|
|
if (frontImage) {
|
|||
|
|
frontImage.src = `images/${newsItem.image}`;
|
|||
|
|
frontImage.alt = newsItem.alt;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新背面图片(保持一致)
|
|||
|
|
const backImage = card.querySelector('.flip-card-back .news-image');
|
|||
|
|
if (backImage) {
|
|||
|
|
backImage.src = `images/${newsItem.image}`;
|
|||
|
|
backImage.alt = newsItem.alt;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新data属性
|
|||
|
|
card.setAttribute('data-news', newsItem.alt);
|
|||
|
|
card.setAttribute('data-image', newsItem.image);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
newsIndex = (newsIndex + 1) % newsData.length;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 卡片点击事件
|
|||
|
|
function addCardListeners() {
|
|||
|
|
const cards = tickerCarousel.querySelectorAll('.ticker-flip-card');
|
|||
|
|
|
|||
|
|
cards.forEach(card => {
|
|||
|
|
card.addEventListener('click', function() {
|
|||
|
|
const newsText = this.getAttribute('data-news');
|
|||
|
|
console.log('点击新闻卡片:', newsText);
|
|||
|
|
// 点击后跳转到微信文章,在新窗口中打开
|
|||
|
|
window.open('https://mp.weixin.qq.com/s/QfUgaORgErymzezvSISlJQ', '_blank');
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 悬停时暂停轮播
|
|||
|
|
card.addEventListener('mouseenter', function() {
|
|||
|
|
clearInterval(newsRotationInterval);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 离开时恢复轮播
|
|||
|
|
card.addEventListener('mouseleave', function() {
|
|||
|
|
startNewsRotation();
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function showNewsInChat(newsText) {
|
|||
|
|
const messageInput = document.getElementById('messageInput');
|
|||
|
|
if (messageInput) {
|
|||
|
|
const cleanText = newsText.replace(/[🔥📚👥🎯🤝🚀💡🌟]/g, '').trim();
|
|||
|
|
messageInput.value = `告诉我更多关于"${cleanText}"的信息`;
|
|||
|
|
messageInput.focus();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 启动新闻轮播
|
|||
|
|
let newsRotationInterval;
|
|||
|
|
function startNewsRotation() {
|
|||
|
|
clearInterval(newsRotationInterval);
|
|||
|
|
newsRotationInterval = setInterval(rotateNews, 5000); // 每5秒轮换一次
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 响应式处理
|
|||
|
|
function handleResize() {
|
|||
|
|
createTickerParticles();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 初始化
|
|||
|
|
createTickerParticles();
|
|||
|
|
addCardListeners();
|
|||
|
|
startNewsRotation();
|
|||
|
|
initMouseFollowFlip(); // 初始化鼠标跟随翻转
|
|||
|
|
|
|||
|
|
window.addEventListener('resize', handleResize);
|
|||
|
|
|
|||
|
|
console.log('鼠标跟随翻转卡片新闻跑马灯初始化完成');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* ===== 聊天管理器相关功能 ===== */
|
|||
|
|
function initChatManagerListeners() {
|
|||
|
|
if (!window.chatManager) return;
|
|||
|
|
|
|||
|
|
// 监听聊天切换事件
|
|||
|
|
window.addEventListener('chatManager:chatSwitched', (e) => {
|
|||
|
|
const { chatId, chat } = e.detail;
|
|||
|
|
console.log('切换到聊天:', chatId);
|
|||
|
|
currentChatId = chatId;
|
|||
|
|
loadChatHistory(chatId);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 监听聊天删除事件
|
|||
|
|
window.addEventListener('chatManager:chatDeleted', (e) => {
|
|||
|
|
const { chatId } = e.detail;
|
|||
|
|
console.log('删除聊天:', chatId);
|
|||
|
|
// 从侧边栏移除
|
|||
|
|
const chatItem = document.querySelector(`[data-chat-id="${chatId}"]`);
|
|||
|
|
if (chatItem) {
|
|||
|
|
chatItem.remove();
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 监听新聊天创建事件
|
|||
|
|
window.addEventListener('chatManager:newChatStarted', () => {
|
|||
|
|
console.log('新聊天事件已触发');
|
|||
|
|
// 不要在这里调用 startNewChat(),避免递归
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 监听聊天标题更新事件
|
|||
|
|
window.addEventListener('chatManager:chatTitleUpdated', (e) => {
|
|||
|
|
const { chatId, title } = e.detail;
|
|||
|
|
console.log('更新聊天标题:', chatId, title);
|
|||
|
|
// 更新侧边栏中的标题
|
|||
|
|
const chatItem = document.querySelector(`[data-chat-id="${chatId}"]`);
|
|||
|
|
if (chatItem) {
|
|||
|
|
const chatTitle = chatItem.querySelector('.chat-title');
|
|||
|
|
if (chatTitle) {
|
|||
|
|
chatTitle.textContent = title;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function loadUserChatHistory() {
|
|||
|
|
// 如果有后端API,从后端加载会话列表
|
|||
|
|
if (window.ChatAPIService) {
|
|||
|
|
try {
|
|||
|
|
const result = await window.ChatAPIService.getConversations();
|
|||
|
|
if (result.success) {
|
|||
|
|
// 清空现有的聊天历史显示
|
|||
|
|
const chatHistoryList = document.querySelector('.chat-history-list');
|
|||
|
|
if (chatHistoryList) {
|
|||
|
|
chatHistoryList.innerHTML = '';
|
|||
|
|
|
|||
|
|
// 添加每个会话到侧边栏
|
|||
|
|
result.conversations.forEach(conversation => {
|
|||
|
|
// 后端返回的会话格式可能是 {id, userId, title, mode, createdAt, updatedAt}
|
|||
|
|
addChatToSidebar(conversation.id, conversation.title || '新对话');
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log(`从后端加载了 ${result.conversations.length} 个会话`);
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('加载会话列表失败:', error);
|
|||
|
|
// 降级到本地存储
|
|||
|
|
loadLocalChatHistory();
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
// 没有后端API,使用本地存储
|
|||
|
|
loadLocalChatHistory();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 从本地存储加载聊天历史
|
|||
|
|
function loadLocalChatHistory() {
|
|||
|
|
if (!window.chatManager) return;
|
|||
|
|
|
|||
|
|
// 获取所有聊天
|
|||
|
|
const chats = window.chatManager.getAllChats();
|
|||
|
|
|
|||
|
|
// 清空现有的聊天历史显示
|
|||
|
|
const chatHistoryList = document.querySelector('.chat-history-list');
|
|||
|
|
if (chatHistoryList) {
|
|||
|
|
chatHistoryList.innerHTML = '';
|
|||
|
|
|
|||
|
|
// 添加每个聊天到侧边栏
|
|||
|
|
chats.forEach(chat => {
|
|||
|
|
addChatToSidebar(chat.id, chat.title);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log(`从本地加载了 ${chats.length} 个聊天历史`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 监听用户登录状态变化
|
|||
|
|
function initUserChangeListener() {
|
|||
|
|
let previousUser = localStorage.getItem('yundage_current_user');
|
|||
|
|
|
|||
|
|
// 监听 storage 事件(其他标签页的变化)
|
|||
|
|
window.addEventListener('storage', function(e) {
|
|||
|
|
if (e.key === 'yundage_current_user') {
|
|||
|
|
handleUserChange(e.oldValue, e.newValue);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 定期检查当前标签页的用户变化
|
|||
|
|
setInterval(() => {
|
|||
|
|
const currentUser = localStorage.getItem('yundage_current_user');
|
|||
|
|
if (currentUser !== previousUser) {
|
|||
|
|
handleUserChange(previousUser, currentUser);
|
|||
|
|
previousUser = currentUser;
|
|||
|
|
}
|
|||
|
|
}, 1000);
|
|||
|
|
|
|||
|
|
// 监听聊天管理器的用户变化事件
|
|||
|
|
window.addEventListener('chatManager:userChanged', () => {
|
|||
|
|
loadUserChatHistory();
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理用户变化
|
|||
|
|
function handleUserChange(oldUserStr, newUserStr) {
|
|||
|
|
console.log('检测到用户变化');
|
|||
|
|
|
|||
|
|
const oldUser = oldUserStr ? JSON.parse(oldUserStr) : null;
|
|||
|
|
const newUser = newUserStr ? JSON.parse(newUserStr) : null;
|
|||
|
|
|
|||
|
|
if (oldUser?.id !== newUser?.id) {
|
|||
|
|
console.log(`用户从 ${oldUser?.username || '未登录'} 切换到 ${newUser?.username || '未登录'}`);
|
|||
|
|
|
|||
|
|
// 重新加载聊天记录
|
|||
|
|
if (window.chatManager) {
|
|||
|
|
window.chatManager.reloadForUser();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 清空当前显示的聊天内容
|
|||
|
|
const chatMessages = document.getElementById('chatMessages');
|
|||
|
|
if (chatMessages) {
|
|||
|
|
chatMessages.innerHTML = '';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 切回欢迎界面
|
|||
|
|
startNewChat();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新loadChatHistory函数以使用新的聊天管理器
|
|||
|
|
async function loadChatHistory(chatId) {
|
|||
|
|
// 清空当前聊天显示
|
|||
|
|
const chatMessages = document.getElementById('chatMessages');
|
|||
|
|
if (chatMessages) {
|
|||
|
|
chatMessages.innerHTML = '';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 设置当前聊天ID
|
|||
|
|
currentChatId = chatId;
|
|||
|
|
|
|||
|
|
// 直接使用本地存储加载消息
|
|||
|
|
// 因为HippoDeepService的getConversationMessages实际上也是从本地chatManager加载的
|
|||
|
|
// 避免重复加载导致消息显示两次
|
|||
|
|
loadLocalChatMessages(chatId);
|
|||
|
|
|
|||
|
|
// 显示聊天容器
|
|||
|
|
showChatContainer();
|
|||
|
|
|
|||
|
|
// 高亮当前聊天项
|
|||
|
|
highlightActiveChatItem(chatId);
|
|||
|
|
|
|||
|
|
// 强制触发一次布局重新计算,确保输入框宽度正确
|
|||
|
|
setTimeout(() => {
|
|||
|
|
const chatModeInput = document.getElementById('chatModeInput');
|
|||
|
|
if (chatModeInput) {
|
|||
|
|
// 触发重新渲染
|
|||
|
|
chatModeInput.style.display = 'none';
|
|||
|
|
void chatModeInput.offsetHeight; // 强制重排
|
|||
|
|
chatModeInput.style.display = 'block';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果有视口处理器,也触发重新计算
|
|||
|
|
if (window.viewportHandler) {
|
|||
|
|
window.viewportHandler.forceRecalculate();
|
|||
|
|
}
|
|||
|
|
}, 50);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 高亮当前活动的聊天项
|
|||
|
|
function highlightActiveChatItem(chatId) {
|
|||
|
|
// 移除所有聊天项的active类
|
|||
|
|
document.querySelectorAll('.chat-history-item').forEach(item => {
|
|||
|
|
item.classList.remove('active');
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 添加active类到当前聊天项
|
|||
|
|
const activeItem = document.querySelector(`[data-chat-id="${chatId}"]`);
|
|||
|
|
if (activeItem) {
|
|||
|
|
activeItem.classList.add('active');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 从本地存储加载聊天消息
|
|||
|
|
function loadLocalChatMessages(chatId) {
|
|||
|
|
// 从聊天管理器加载消息
|
|||
|
|
if (window.chatManager) {
|
|||
|
|
console.log(`[loadChatHistory] 从本地加载聊天: ${chatId}`);
|
|||
|
|
console.log(`[loadChatHistory] 当前chatManager.currentChatId: ${window.chatManager.currentChatId}`);
|
|||
|
|
|
|||
|
|
// 如果不是当前聊天,才需要切换
|
|||
|
|
if (window.chatManager.currentChatId !== chatId) {
|
|||
|
|
console.log(`[loadChatHistory] 切换到聊天: ${chatId}`);
|
|||
|
|
window.chatManager.switchToChat(chatId);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const chat = window.chatManager.getChat(chatId);
|
|||
|
|
if (chat && chat.messages) {
|
|||
|
|
const chatMessages = document.getElementById('chatMessages');
|
|||
|
|
chat.messages.forEach(message => {
|
|||
|
|
// 直接创建消息元素,不调用addMessage以避免重复保存
|
|||
|
|
const messageItem = document.createElement('div');
|
|||
|
|
messageItem.className = 'message-item mb-4';
|
|||
|
|
|
|||
|
|
const isUser = message.type === 'user';
|
|||
|
|
const messageTime = new Date(message.timestamp).toLocaleTimeString('zh-CN', {
|
|||
|
|
hour: '2-digit',
|
|||
|
|
minute: '2-digit'
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 渲染内容(支持Markdown)
|
|||
|
|
let renderedContent = message.content;
|
|||
|
|
if (window.markdownRenderer) {
|
|||
|
|
renderedContent = window.markdownRenderer.render(message.content);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (isUser) {
|
|||
|
|
messageItem.innerHTML = `
|
|||
|
|
<div class="flex justify-end">
|
|||
|
|
<div class="message user-message px-4 py-3">
|
|||
|
|
<div class="text-sm text-gray-800 markdown-content">${renderedContent}</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="text-xs text-gray-500 text-right mt-1">${messageTime}</div>
|
|||
|
|
`;
|
|||
|
|
} else {
|
|||
|
|
messageItem.innerHTML = `
|
|||
|
|
<div class="flex justify-start">
|
|||
|
|
<div class="message ai-message px-4 py-3">
|
|||
|
|
<div class="text-sm text-gray-800 markdown-content">${renderedContent}</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="text-xs text-gray-500 mt-1">${messageTime}</div>
|
|||
|
|
`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
chatMessages.appendChild(messageItem);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 滚动到底部
|
|||
|
|
chatMessages.scrollTop = chatMessages.scrollHeight;
|
|||
|
|
|
|||
|
|
// 切换到聊天模式
|
|||
|
|
showChatContainer();
|
|||
|
|
|
|||
|
|
// 强制重新计算布局,确保对齐
|
|||
|
|
setTimeout(() => {
|
|||
|
|
if (window.viewportHandler) {
|
|||
|
|
window.viewportHandler.forceRecalculate();
|
|||
|
|
}
|
|||
|
|
}, 100);
|
|||
|
|
} else {
|
|||
|
|
// 即使没有消息也要切换到聊天模式
|
|||
|
|
showChatContainer();
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
// 兼容旧的加载方式
|
|||
|
|
loadChatHistoryLegacy(chatId);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 保留旧的加载方式作为后备
|
|||
|
|
function loadChatHistoryLegacy(chatId) {
|
|||
|
|
// 原来的loadChatHistory实现
|
|||
|
|
if (chatHistoryData.has(chatId)) {
|
|||
|
|
const messages = chatHistoryData.get(chatId);
|
|||
|
|
const chatMessages = document.getElementById('chatMessages');
|
|||
|
|
|
|||
|
|
messages.forEach(message => {
|
|||
|
|
const messageItem = document.createElement('div');
|
|||
|
|
messageItem.className = 'message-item mb-4';
|
|||
|
|
|
|||
|
|
const isUser = message.type === 'user';
|
|||
|
|
const messageTime = new Date(message.timestamp).toLocaleTimeString('zh-CN', {
|
|||
|
|
hour: '2-digit',
|
|||
|
|
minute: '2-digit'
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (isUser) {
|
|||
|
|
messageItem.innerHTML = `
|
|||
|
|
<div class="flex justify-end">
|
|||
|
|
<div class="message user-message px-4 py-3">
|
|||
|
|
<p class="text-sm text-gray-800">${message.content}</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="text-xs text-gray-500 text-right mt-1">${messageTime}</div>
|
|||
|
|
`;
|
|||
|
|
} else {
|
|||
|
|
messageItem.innerHTML = `
|
|||
|
|
<div class="flex justify-start">
|
|||
|
|
<div class="message ai-message px-4 py-3">
|
|||
|
|
<p class="text-sm text-gray-800">${message.content}</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="text-xs text-gray-500 mt-1">${messageTime}</div>
|
|||
|
|
`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
chatMessages.appendChild(messageItem);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
chatMessages.scrollTop = chatMessages.scrollHeight;
|
|||
|
|
showChatContainer();
|
|||
|
|
|
|||
|
|
// 强制重新计算布局,确保对齐
|
|||
|
|
setTimeout(() => {
|
|||
|
|
if (window.viewportHandler) {
|
|||
|
|
window.viewportHandler.forceRecalculate();
|
|||
|
|
}
|
|||
|
|
}, 100);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 全局暴露必要的函数
|
|||
|
|
window.startNewChat = startNewChat;
|
|||
|
|
window.toggleDeepSearch = toggleDeepSearch;
|
|||
|
|
|
|||
|
|
/* ===== Apple风格背景动画 ===== */
|
|||
|
|
function initBackground() {
|
|||
|
|
const gradientElement = document.getElementById('animatedGradient');
|
|||
|
|
if (!gradientElement) return;
|
|||
|
|
|
|||
|
|
let time = 0;
|
|||
|
|
|
|||
|
|
const animateGradient = () => {
|
|||
|
|
// 如果背景动画被暂停,延迟检查是否恢复
|
|||
|
|
if (window.pauseBackgroundAnimation) {
|
|||
|
|
setTimeout(() => {
|
|||
|
|
requestAnimationFrame(animateGradient);
|
|||
|
|
}, 1000); // 每秒检查一次
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
time += 0.0056; // 减慢动画速度让过渡更平滑
|
|||
|
|
|
|||
|
|
// 柔和、低饱和度的冷色调,包含近白色
|
|||
|
|
const colors = [
|
|||
|
|
{ r: 180, g: 205, b: 215, a: 0.5 }, // 柔和蓝
|
|||
|
|
{ r: 160, g: 195, b: 220, a: 0.4 }, // 柔和天蓝
|
|||
|
|
{ r: 160, g: 220, b: 200, a: 0.35 }, // 柔和海蓝
|
|||
|
|
{ r: 240, g: 248, b: 255, a: 0.6 }, // 爱丽丝蓝 (近白)
|
|||
|
|
{ r: 210, g: 210, b: 230, a: 0.3 }, // 柔和薰衣草
|
|||
|
|
{ r: 190, g: 140, b: 200, a: 0.25 } // 柔和紫
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
// 平滑的颜色插值函数
|
|||
|
|
const lerpColor = (color1, color2, t) => ({
|
|||
|
|
r: Math.round(color1.r + (color2.r - color1.r) * t),
|
|||
|
|
g: Math.round(color1.g + (color2.g - color1.g) * t),
|
|||
|
|
b: Math.round(color1.b + (color2.b - color1.b) * t),
|
|||
|
|
a: color1.a + (color2.a - color1.a) * t
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 为每个渐变创建平滑的颜色变化
|
|||
|
|
const getInterpolatedColor = (colorIndex, speed) => {
|
|||
|
|
const totalColors = colors.length;
|
|||
|
|
const colorPhase = (time * speed) % totalColors;
|
|||
|
|
const currentIndex = Math.floor(colorPhase) % totalColors;
|
|||
|
|
const nextIndex = (currentIndex + 1) % totalColors;
|
|||
|
|
const t = colorPhase - Math.floor(colorPhase);
|
|||
|
|
|
|||
|
|
return lerpColor(colors[currentIndex], colors[nextIndex], t);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 创建多个动画渐变,使用平滑的三角函数
|
|||
|
|
const gradient1Size = 60 + Math.sin(time * 0.8) * 20;
|
|||
|
|
const gradient2Size = 50 + Math.cos(time * 1.1) * 15;
|
|||
|
|
const gradient3Size = 70 + Math.sin(time * 0.6) * 25;
|
|||
|
|
const gradient4Size = 45 + Math.cos(time * 1.3) * 18;
|
|||
|
|
|
|||
|
|
// 更平滑的位置变化
|
|||
|
|
const gradient1X = 30 + Math.sin(time * 0.3) * 25;
|
|||
|
|
const gradient1Y = 40 + Math.cos(time * 0.4) * 20;
|
|||
|
|
|
|||
|
|
const gradient2X = 70 + Math.cos(time * 0.35) * 30;
|
|||
|
|
const gradient2Y = 60 + Math.sin(time * 0.25) * 25;
|
|||
|
|
|
|||
|
|
const gradient3X = 20 + Math.sin(time * 0.45) * 35;
|
|||
|
|
const gradient3Y = 80 + Math.cos(time * 0.2) * 30;
|
|||
|
|
|
|||
|
|
const gradient4X = 80 + Math.cos(time * 0.5) * 25;
|
|||
|
|
const gradient4Y = 20 + Math.sin(time * 0.3) * 20;
|
|||
|
|
|
|||
|
|
// 使用插值后的颜色
|
|||
|
|
const color1 = getInterpolatedColor(0, 0.3);
|
|||
|
|
const color2 = getInterpolatedColor(1, 0.4);
|
|||
|
|
const color3 = getInterpolatedColor(2, 0.2);
|
|||
|
|
const color4 = getInterpolatedColor(3, 0.5);
|
|||
|
|
|
|||
|
|
// 添加额外的颜色点以增强流动感
|
|||
|
|
const gradient5Size = 55 + Math.sin(time * 0.9) * 18;
|
|||
|
|
const gradient6Size = 65 + Math.cos(time * 0.7) * 22;
|
|||
|
|
|
|||
|
|
const gradient5X = 50 + Math.cos(time * 0.4) * 20;
|
|||
|
|
const gradient5Y = 30 + Math.sin(time * 0.5) * 15;
|
|||
|
|
|
|||
|
|
const gradient6X = 40 + Math.sin(time * 0.6) * 28;
|
|||
|
|
const gradient6Y = 70 + Math.cos(time * 0.35) * 22;
|
|||
|
|
|
|||
|
|
const color5 = getInterpolatedColor(4, 0.35);
|
|||
|
|
const color6 = getInterpolatedColor(5, 0.45);
|
|||
|
|
|
|||
|
|
const backgroundStyle = `
|
|||
|
|
radial-gradient(${gradient1Size}% ${gradient1Size}% at ${gradient1X}% ${gradient1Y}%,
|
|||
|
|
rgba(${color1.r}, ${color1.g}, ${color1.b}, ${color1.a}) 0%,
|
|||
|
|
transparent 60%),
|
|||
|
|
radial-gradient(${gradient2Size}% ${gradient2Size}% at ${gradient2X}% ${gradient2Y}%,
|
|||
|
|
rgba(${color2.r}, ${color2.g}, ${color2.b}, ${color2.a}) 0%,
|
|||
|
|
transparent 65%),
|
|||
|
|
radial-gradient(${gradient3Size}% ${gradient3Size}% at ${gradient3X}% ${gradient3Y}%,
|
|||
|
|
rgba(${color3.r}, ${color3.g}, ${color3.b}, ${color3.a}) 0%,
|
|||
|
|
transparent 70%),
|
|||
|
|
radial-gradient(${gradient4Size}% ${gradient4Size}% at ${gradient4X}% ${gradient4Y}%,
|
|||
|
|
rgba(${color4.r}, ${color4.g}, ${color4.b}, ${color4.a}) 0%,
|
|||
|
|
transparent 55%),
|
|||
|
|
radial-gradient(${gradient5Size}% ${gradient5Size}% at ${gradient5X}% ${gradient5Y}%,
|
|||
|
|
rgba(${color5.r}, ${color5.g}, ${color5.b}, ${color5.a}) 0%,
|
|||
|
|
transparent 75%),
|
|||
|
|
radial-gradient(${gradient6Size}% ${gradient6Size}% at ${gradient6X}% ${gradient6Y}%,
|
|||
|
|
rgba(${color6.r}, ${color6.g}, ${color6.b}, ${color6.a}) 0%,
|
|||
|
|
transparent 68%),
|
|||
|
|
linear-gradient(135deg,
|
|||
|
|
rgba(248, 250, 252, 0.9) 0%,
|
|||
|
|
rgba(241, 245, 249, 0.8) 50%,
|
|||
|
|
rgba(226, 232, 240, 0.9) 100%)
|
|||
|
|
`;
|
|||
|
|
|
|||
|
|
gradientElement.style.background = backgroundStyle;
|
|||
|
|
requestAnimationFrame(animateGradient);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
requestAnimationFrame(animateGradient);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ===== 初始化工具下拉菜单 =====
|
|||
|
|
function initToolsDropdown() {
|
|||
|
|
// 存储状态
|
|||
|
|
let isDeepResearchEnabled = false;
|
|||
|
|
let isShowThinkingEnabled = false;
|
|||
|
|
|
|||
|
|
// 欢迎模式的工具按钮和下拉菜单
|
|||
|
|
const toolsMenuBtn = document.getElementById('toolsMenuBtn');
|
|||
|
|
const toolsDropdown = document.getElementById('toolsDropdown');
|
|||
|
|
const deepResearchItem = document.getElementById('deepResearchItem');
|
|||
|
|
const showThinkingItem = document.getElementById('showThinkingItem');
|
|||
|
|
const deepResearchStatus = document.getElementById('deepResearchStatus');
|
|||
|
|
const showThinkingStatus = document.getElementById('showThinkingStatus');
|
|||
|
|
|
|||
|
|
// 聊天模式的工具按钮和下拉菜单
|
|||
|
|
const chatModeToolsMenuBtn = document.getElementById('chatModeToolsMenuBtn');
|
|||
|
|
const chatModeToolsDropdown = document.getElementById('chatModeToolsDropdown');
|
|||
|
|
const chatModeDeepResearchItem = document.getElementById('chatModeDeepResearchItem');
|
|||
|
|
const chatModeShowThinkingItem = document.getElementById('chatModeShowThinkingItem');
|
|||
|
|
const chatModeDeepResearchStatus = document.getElementById('chatModeDeepResearchStatus');
|
|||
|
|
const chatModeShowThinkingStatus = document.getElementById('chatModeShowThinkingStatus');
|
|||
|
|
|
|||
|
|
// 点击按钮显示/隐藏下拉菜单
|
|||
|
|
if (toolsMenuBtn && toolsDropdown) {
|
|||
|
|
toolsMenuBtn.addEventListener('click', function(e) {
|
|||
|
|
e.preventDefault();
|
|||
|
|
e.stopPropagation();
|
|||
|
|
const isShowing = toolsDropdown.classList.contains('show');
|
|||
|
|
|
|||
|
|
if (isShowing) {
|
|||
|
|
toolsDropdown.classList.remove('show');
|
|||
|
|
toolsMenuBtn.classList.remove('active');
|
|||
|
|
setTimeout(() => {
|
|||
|
|
toolsDropdown.style.display = 'none';
|
|||
|
|
}, 200);
|
|||
|
|
} else {
|
|||
|
|
toolsDropdown.style.display = 'block';
|
|||
|
|
setTimeout(() => {
|
|||
|
|
toolsDropdown.classList.add('show');
|
|||
|
|
toolsMenuBtn.classList.add('active');
|
|||
|
|
}, 10);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 聊天模式工具按钮
|
|||
|
|
if (chatModeToolsMenuBtn && chatModeToolsDropdown) {
|
|||
|
|
chatModeToolsMenuBtn.addEventListener('click', function(e) {
|
|||
|
|
e.stopPropagation();
|
|||
|
|
const isShowing = chatModeToolsDropdown.classList.contains('show');
|
|||
|
|
|
|||
|
|
if (isShowing) {
|
|||
|
|
chatModeToolsDropdown.classList.remove('show');
|
|||
|
|
chatModeToolsMenuBtn.classList.remove('active');
|
|||
|
|
setTimeout(() => {
|
|||
|
|
chatModeToolsDropdown.style.display = 'none';
|
|||
|
|
}, 200);
|
|||
|
|
} else {
|
|||
|
|
chatModeToolsDropdown.style.display = 'block';
|
|||
|
|
setTimeout(() => {
|
|||
|
|
chatModeToolsDropdown.classList.add('show');
|
|||
|
|
chatModeToolsMenuBtn.classList.add('active');
|
|||
|
|
}, 10);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 点击概念感知模式
|
|||
|
|
let isDeepResearchToggling = false; // 防止重复触发
|
|||
|
|
function toggleDeepResearch(e) {
|
|||
|
|
// 如果事件存在,阻止冒泡
|
|||
|
|
if (e) {
|
|||
|
|
e.stopPropagation();
|
|||
|
|
e.stopImmediatePropagation();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 防止重复触发
|
|||
|
|
if (isDeepResearchToggling) return;
|
|||
|
|
isDeepResearchToggling = true;
|
|||
|
|
|
|||
|
|
isDeepResearchEnabled = !isDeepResearchEnabled;
|
|||
|
|
|
|||
|
|
// 更新状态文字
|
|||
|
|
if (deepResearchStatus) {
|
|||
|
|
deepResearchStatus.textContent = isDeepResearchEnabled ? '开' : '关';
|
|||
|
|
deepResearchStatus.style.color = isDeepResearchEnabled ? '#2563eb' : '#666';
|
|||
|
|
}
|
|||
|
|
if (chatModeDeepResearchStatus) {
|
|||
|
|
chatModeDeepResearchStatus.textContent = isDeepResearchEnabled ? '开' : '关';
|
|||
|
|
chatModeDeepResearchStatus.style.color = isDeepResearchEnabled ? '#2563eb' : '#666';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新菜单项样式
|
|||
|
|
if (isDeepResearchEnabled) {
|
|||
|
|
if (deepResearchItem) deepResearchItem.classList.add('active');
|
|||
|
|
if (chatModeDeepResearchItem) chatModeDeepResearchItem.classList.add('active');
|
|||
|
|
} else {
|
|||
|
|
if (deepResearchItem) deepResearchItem.classList.remove('active');
|
|||
|
|
if (chatModeDeepResearchItem) chatModeDeepResearchItem.classList.remove('active');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 同步更新两个开关的状态
|
|||
|
|
const deepResearchToggle = document.getElementById('deepResearchToggle');
|
|||
|
|
const chatModeDeepResearchToggle = document.getElementById('chatModeDeepResearchToggle');
|
|||
|
|
|
|||
|
|
if (deepResearchToggle && deepResearchToggle.checked !== isDeepResearchEnabled) {
|
|||
|
|
deepResearchToggle.checked = isDeepResearchEnabled;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (chatModeDeepResearchToggle && chatModeDeepResearchToggle.checked !== isDeepResearchEnabled) {
|
|||
|
|
chatModeDeepResearchToggle.checked = isDeepResearchEnabled;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log('概念感知模式:', isDeepResearchEnabled ? '开启' : '关闭');
|
|||
|
|
|
|||
|
|
// 延迟重置标志
|
|||
|
|
setTimeout(() => {
|
|||
|
|
isDeepResearchToggling = false;
|
|||
|
|
}, 100);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 点击显示思考
|
|||
|
|
let isShowThinkingToggling = false; // 防止重复触发
|
|||
|
|
function toggleShowThinking(e) {
|
|||
|
|
// 如果事件存在,阻止冒泡
|
|||
|
|
if (e) {
|
|||
|
|
e.stopPropagation();
|
|||
|
|
e.stopImmediatePropagation();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 防止重复触发
|
|||
|
|
if (isShowThinkingToggling) return;
|
|||
|
|
isShowThinkingToggling = true;
|
|||
|
|
|
|||
|
|
isShowThinkingEnabled = !isShowThinkingEnabled;
|
|||
|
|
|
|||
|
|
// 更新状态文字
|
|||
|
|
if (showThinkingStatus) {
|
|||
|
|
showThinkingStatus.textContent = isShowThinkingEnabled ? '开' : '关';
|
|||
|
|
showThinkingStatus.style.color = isShowThinkingEnabled ? '#2563eb' : '#666';
|
|||
|
|
}
|
|||
|
|
if (chatModeShowThinkingStatus) {
|
|||
|
|
chatModeShowThinkingStatus.textContent = isShowThinkingEnabled ? '开' : '关';
|
|||
|
|
chatModeShowThinkingStatus.style.color = isShowThinkingEnabled ? '#2563eb' : '#666';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新菜单项样式
|
|||
|
|
if (isShowThinkingEnabled) {
|
|||
|
|
if (showThinkingItem) showThinkingItem.classList.add('active');
|
|||
|
|
if (chatModeShowThinkingItem) chatModeShowThinkingItem.classList.add('active');
|
|||
|
|
} else {
|
|||
|
|
if (showThinkingItem) showThinkingItem.classList.remove('active');
|
|||
|
|
if (chatModeShowThinkingItem) chatModeShowThinkingItem.classList.remove('active');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log('显示思考:', isShowThinkingEnabled ? '开启' : '关闭');
|
|||
|
|
|
|||
|
|
// 延迟重置标志
|
|||
|
|
setTimeout(() => {
|
|||
|
|
isShowThinkingToggling = false;
|
|||
|
|
}, 100);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 绑定点击事件 - 只绑定到开关本身,避免重复触发
|
|||
|
|
const deepResearchToggle = document.getElementById('deepResearchToggle');
|
|||
|
|
const showThinkingToggle = document.getElementById('showThinkingToggle');
|
|||
|
|
const chatModeDeepResearchToggle = document.getElementById('chatModeDeepResearchToggle');
|
|||
|
|
const chatModeShowThinkingToggle = document.getElementById('chatModeShowThinkingToggle');
|
|||
|
|
|
|||
|
|
if (deepResearchToggle) {
|
|||
|
|
deepResearchToggle.addEventListener('change', function(e) {
|
|||
|
|
e.stopPropagation();
|
|||
|
|
toggleDeepResearch();
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (showThinkingToggle) {
|
|||
|
|
showThinkingToggle.addEventListener('change', function(e) {
|
|||
|
|
e.stopPropagation();
|
|||
|
|
toggleShowThinking();
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (chatModeDeepResearchToggle) {
|
|||
|
|
chatModeDeepResearchToggle.addEventListener('change', function(e) {
|
|||
|
|
e.stopPropagation();
|
|||
|
|
toggleDeepResearch();
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (chatModeShowThinkingToggle) {
|
|||
|
|
chatModeShowThinkingToggle.addEventListener('change', function(e) {
|
|||
|
|
e.stopPropagation();
|
|||
|
|
toggleShowThinking();
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 点击其他地方关闭下拉菜单
|
|||
|
|
document.addEventListener('click', function(e) {
|
|||
|
|
if (toolsDropdown && !toolsMenuBtn.contains(e.target) && !toolsDropdown.contains(e.target)) {
|
|||
|
|
toolsDropdown.classList.remove('show');
|
|||
|
|
toolsMenuBtn.classList.remove('active');
|
|||
|
|
setTimeout(() => {
|
|||
|
|
toolsDropdown.style.display = 'none';
|
|||
|
|
}, 200);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (chatModeToolsDropdown && !chatModeToolsMenuBtn.contains(e.target) && !chatModeToolsDropdown.contains(e.target)) {
|
|||
|
|
chatModeToolsDropdown.classList.remove('show');
|
|||
|
|
chatModeToolsMenuBtn.classList.remove('active');
|
|||
|
|
setTimeout(() => {
|
|||
|
|
chatModeToolsDropdown.style.display = 'none';
|
|||
|
|
}, 200);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 导出获取状态的方法
|
|||
|
|
window.getToolsSettings = function() {
|
|||
|
|
return {
|
|||
|
|
deepResearch: isDeepResearchEnabled,
|
|||
|
|
showThinking: isShowThinkingEnabled
|
|||
|
|
};
|
|||
|
|
};
|
|||
|
|
}
|