first commit

This commit is contained in:
闫旭隆
2025-10-17 09:31:28 +08:00
commit 4698145045
589 changed files with 196795 additions and 0 deletions

View File

@ -0,0 +1,480 @@
/**
* 首页认证集成 - 处理登录状态检查和用户菜单
*/
class HomeAuthIntegration {
constructor() {
this.currentUser = null;
this.isAuthenticated = false;
this.init();
}
/**
* 初始化
*/
async init() {
await this.checkAuthStatus();
this.setupEventListeners();
this.updateUI();
// 隐藏加载遮罩
const loader = document.getElementById('authCheckLoader');
if (loader) {
loader.style.display = 'none';
}
}
/**
* 检查认证状态
*/
async checkAuthStatus() {
try {
const authStatus = await AuthService.checkAuthStatus();
this.isAuthenticated = authStatus.isAuthenticated;
this.currentUser = authStatus.user;
// 如果用户已登录保存用户信息到localStorage供ChatManager使用
if (this.isAuthenticated && this.currentUser) {
localStorage.setItem('yundage_current_user', JSON.stringify(this.currentUser));
} else {
// 如果用户未登录,清除本地存储的用户信息
localStorage.removeItem('yundage_current_user');
}
console.log('Auth Status:', authStatus);
// 触发认证状态检查完成事件
window.dispatchEvent(new CustomEvent('authStatusChecked', {
detail: {
isAuthenticated: this.isAuthenticated,
user: this.currentUser
}
}));
// 路由保护逻辑
this.handleRouteProtection();
} catch (error) {
console.error('Failed to check auth status:', error);
this.isAuthenticated = false;
this.currentUser = null;
// 清除本地存储的用户信息
localStorage.removeItem('yundage_current_user');
// 即使检查失败也要触发事件
window.dispatchEvent(new CustomEvent('authStatusChecked', {
detail: {
isAuthenticated: false,
user: null,
error: error
}
}));
// 检查失败时也要处理路由保护
this.handleRouteProtection();
}
}
/**
* 处理路由保护
*/
handleRouteProtection() {
const currentPath = window.location.pathname;
const isAuthPage = currentPath.includes('/auth/pages/');
const isMainPage = currentPath.includes('/index.html') ||
currentPath === '/' ||
currentPath.endsWith('/yundage/') ||
currentPath.endsWith('/yundage');
// 如果在登录页面且已登录,跳转到首页
if (this.isAuthenticated && isAuthPage) {
console.log('已登录用户访问登录页面,跳转到首页');
setTimeout(() => {
window.location.href = '../../index.html';
}, 500);
return;
}
// 如果在主页且未登录,跳转到登录页面
if (!this.isAuthenticated && isMainPage) {
console.log('未登录用户访问主页,跳转到登录页面');
// 更新加载遮罩文字
const loader = document.getElementById('authCheckLoader');
if (loader) {
const loaderText = loader.querySelector('p');
if (loaderText) {
loaderText.textContent = '正在跳转到登录页面...';
}
}
// 显示提示
this.showNotification('请先登录', 'info');
// 短暂延迟后跳转
setTimeout(() => {
window.location.href = 'auth/pages/login.html?redirect=' + encodeURIComponent(window.location.href);
}, 500);
return;
}
}
/**
* 设置事件监听器
*/
setupEventListeners() {
// 用户菜单切换
const userMenuToggle = document.getElementById('userMenuToggle');
if (userMenuToggle) {
userMenuToggle.addEventListener('click', (e) => {
e.stopPropagation();
this.toggleUserMenu();
});
}
// 退出登录按钮
const logoutBtn = document.getElementById('logoutBtn');
if (logoutBtn) {
logoutBtn.addEventListener('click', (e) => {
e.preventDefault();
this.handleLogout();
});
}
// 点击页面其他地方关闭菜单
document.addEventListener('click', (e) => {
const userDropdown = document.getElementById('userDropdown');
const userMenuToggle = document.getElementById('userMenuToggle');
if (userDropdown && userMenuToggle) {
if (!userMenuToggle.contains(e.target) && !userDropdown.contains(e.target)) {
userDropdown.classList.add('hidden');
}
}
});
// 监听存储变化(其他标签页登录/登出)
window.addEventListener('storage', (e) => {
if (e.key === TokenManager.TOKEN_KEY || e.key === TokenManager.USER_KEY) {
this.handleStorageChange();
}
});
// 定期检查token状态
this.startTokenCheck();
}
/**
* 更新UI显示
*/
updateUI() {
const guestMenu = document.getElementById('guestMenu');
const userMenu = document.getElementById('userMenu');
const userName = document.getElementById('userName');
const userAccount = document.getElementById('userAccount');
if (this.isAuthenticated && this.currentUser) {
// 显示已登录状态
if (guestMenu) guestMenu.classList.add('hidden');
if (userMenu) userMenu.classList.remove('hidden');
// 更新用户信息显示
if (userName) {
userName.textContent = this.currentUser.username || this.currentUser.nickname || '用户';
}
if (userAccount) {
userAccount.textContent = this.currentUser.phone || this.currentUser.email || '';
}
// 更新用户头像(可选)
this.updateUserAvatar();
// 添加刷新按钮事件(如果存在)
this.setupRefreshButton();
} else {
// 显示未登录状态
if (guestMenu) guestMenu.classList.remove('hidden');
if (userMenu) userMenu.classList.add('hidden');
}
}
/**
* 设置刷新按钮
*/
setupRefreshButton() {
const refreshBtn = document.getElementById('refreshUserInfo');
if (refreshBtn && !refreshBtn._listenerAdded) {
refreshBtn._listenerAdded = true;
refreshBtn.addEventListener('click', async (e) => {
e.preventDefault();
await this.refreshUserInfo();
});
}
}
/**
* 刷新用户信息
*/
async refreshUserInfo() {
try {
console.log('手动刷新用户信息...');
const result = await AuthService.getUserInfo();
if (result.success && result.data) {
this.currentUser = result.data;
// 更新本地存储
localStorage.setItem('yundage_current_user', JSON.stringify(this.currentUser));
// 更新UI
this.updateUI();
// 显示成功提示
this.showNotification('用户信息已更新', 'success');
} else {
throw new Error(result.message || '获取用户信息失败');
}
} catch (error) {
console.error('刷新用户信息失败:', error);
// 特殊处理不同类型的错误
if (error.message.includes('403')) {
this.showNotification('没有权限访问此用户信息', 'warning');
} else if (error.message.includes('404')) {
this.showNotification('用户不存在', 'error');
} else {
this.showNotification('刷新失败:' + error.message, 'error');
}
}
}
/**
* 显示通知消息
*/
showNotification(message, type = 'info') {
// 创建通知元素
const notification = document.createElement('div');
notification.className = `fixed top-4 right-4 p-4 rounded-lg shadow-lg z-50 transition-all duration-300 transform translate-x-full`;
// 根据类型设置样式
const typeStyles = {
success: 'bg-green-500 text-white',
error: 'bg-red-500 text-white',
warning: 'bg-yellow-500 text-white',
info: 'bg-blue-500 text-white'
};
notification.className += ` ${typeStyles[type] || typeStyles.info}`;
notification.textContent = message;
// 添加到页面
document.body.appendChild(notification);
// 显示动画
setTimeout(() => {
notification.classList.remove('translate-x-full');
}, 100);
// 3秒后自动消失
setTimeout(() => {
notification.classList.add('translate-x-full');
setTimeout(() => {
notification.remove();
}, 300);
}, 3000);
}
/**
* 切换用户菜单显示
*/
toggleUserMenu() {
const userDropdown = document.getElementById('userDropdown');
if (userDropdown) {
userDropdown.classList.toggle('hidden');
}
}
/**
* 处理退出登录
*/
async handleLogout() {
try {
// 显示确认对话框
const confirmed = confirm('确定要退出登录吗?');
if (!confirmed) return;
// 执行登出
const result = await AuthService.logout();
if (result.success) {
this.isAuthenticated = false;
this.currentUser = null;
// 清除localStorage中的用户信息
localStorage.removeItem('yundage_current_user');
this.updateUI();
// 隐藏用户菜单
const userDropdown = document.getElementById('userDropdown');
if (userDropdown) {
userDropdown.classList.add('hidden');
}
// 显示成功提示
this.showNotification('已退出登录', 'success');
// 可选:刷新页面
setTimeout(() => {
window.location.reload();
}, 1000);
} else {
this.showNotification(result.message || '退出登录失败', 'error');
}
} catch (error) {
console.error('Logout error:', error);
this.showNotification('退出登录失败', 'error');
}
}
/**
* 处理存储变化(跨标签页同步)
*/
async handleStorageChange() {
await this.checkAuthStatus();
this.updateUI();
}
/**
* 更新用户头像
*/
updateUserAvatar() {
// 如果用户有头像,可以在这里更新头像显示
// 当前使用默认的太阳云朵图标
const userMenuToggle = document.getElementById('userMenuToggle');
if (userMenuToggle && this.currentUser && this.currentUser.avatar) {
// 可以在这里添加自定义头像逻辑
userMenuToggle.style.backgroundImage = `url(${this.currentUser.avatar})`;
userMenuToggle.style.backgroundSize = 'cover';
}
}
/**
* 显示通知消息
* @param {string} message - 消息内容
* @param {string} type - 消息类型success/error/info/warning
*/
showNotification(message, type = 'info') {
// 创建通知元素
const notification = document.createElement('div');
notification.className = `fixed top-20 right-4 z-50 px-4 py-3 rounded-lg shadow-lg max-w-sm transition-all duration-300 transform translate-x-full`;
// 设置样式
switch (type) {
case 'success':
notification.classList.add('bg-green-500', 'text-white');
break;
case 'error':
notification.classList.add('bg-red-500', 'text-white');
break;
case 'warning':
notification.classList.add('bg-yellow-500', 'text-white');
break;
default:
notification.classList.add('bg-blue-500', 'text-white');
}
notification.innerHTML = `
<div class="flex items-center gap-2">
<i class="fas fa-${type === 'success' ? 'check-circle' : type === 'error' ? 'exclamation-circle' : 'info-circle'}"></i>
<span>${message}</span>
<button class="ml-2 text-white hover:text-gray-200" onclick="this.parentElement.parentElement.remove()">
<i class="fas fa-times"></i>
</button>
</div>
`;
document.body.appendChild(notification);
// 显示动画
setTimeout(() => {
notification.classList.remove('translate-x-full');
}, 100);
// 自动隐藏
setTimeout(() => {
notification.classList.add('translate-x-full');
setTimeout(() => {
if (notification.parentElement) {
notification.remove();
}
}, 300);
}, 3000);
}
/**
* 开始token检查定时器
*/
startTokenCheck() {
// 每5分钟检查一次token状态
setInterval(async () => {
const isValid = TokenManager.isTokenValid();
if (this.isAuthenticated && !isValid) {
// Token已过期尝试刷新
const refreshResult = await AuthService.refreshToken();
if (!refreshResult.success) {
// 刷新失败,清除登录状态
this.isAuthenticated = false;
this.currentUser = null;
this.updateUI();
this.showNotification('登录已过期,请重新登录', 'warning');
}
}
}, 5 * 60 * 1000); // 5分钟
}
/**
* 检查是否需要登录才能使用某些功能
* @param {Function} callback - 需要登录后执行的回调函数
* @param {string} redirectUrl - 登录后的回跳地址
*/
requireAuth(callback, redirectUrl = null) {
if (this.isAuthenticated) {
callback();
} else {
// 未登录,跳转到登录页面
const loginUrl = redirectUrl
? `auth/pages/login.html?redirect=${encodeURIComponent(redirectUrl)}`
: 'auth/pages/login.html';
window.location.href = loginUrl;
}
}
/**
* 获取当前用户信息
* @returns {object|null} 用户信息
*/
getCurrentUser() {
return this.currentUser;
}
/**
* 检查是否已登录
* @returns {boolean} 是否已登录
*/
isLoggedIn() {
return this.isAuthenticated;
}
}
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', () => {
window.homeAuth = new HomeAuthIntegration();
});
// 导出供其他模块使用
if (typeof module !== 'undefined' && module.exports) {
module.exports = HomeAuthIntegration;
}

View File

@ -0,0 +1,2 @@
[ZoneTransfer]
ZoneId=3

View File

@ -0,0 +1,71 @@
/**
* 动态监测聊天输入框高度变化
* 当输入框高度变化时如多行文本扩展自动更新CSS变量
*/
(function() {
'use strict';
document.addEventListener('DOMContentLoaded', function() {
// 获取聊天输入框容器
const chatInputContainer = document.getElementById('chatModeInput');
if (!chatInputContainer) {
console.log('Chat input container not found, will retry when chat mode is activated');
return;
}
// 创建 ResizeObserver 监测高度变化
const resizeObserver = new ResizeObserver(entries => {
for (let entry of entries) {
const height = entry.contentRect.height;
// 更新CSS变量
document.documentElement.style.setProperty('--chat-input-height', height + 'px');
// 调试信息
console.log('Chat input height updated:', height + 'px');
}
});
// 开始观察
resizeObserver.observe(chatInputContainer);
// 初始设置高度
const initialHeight = chatInputContainer.offsetHeight;
if (initialHeight > 0) {
document.documentElement.style.setProperty('--chat-input-height', initialHeight + 'px');
}
// 监听聊天模式切换
const observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
// 检查是否进入聊天模式
if (document.body.classList.contains('chat-mode')) {
// 重新计算高度
setTimeout(() => {
const chatInput = document.getElementById('chatModeInput');
if (chatInput) {
const height = chatInput.offsetHeight;
document.documentElement.style.setProperty('--chat-input-height', height + 'px');
}
}, 100);
}
}
});
});
// 观察body的class变化
observer.observe(document.body, { attributes: true });
});
// 处理窗口大小变化
window.addEventListener('resize', function() {
const chatInputContainer = document.getElementById('chatModeInput');
if (chatInputContainer && document.body.classList.contains('chat-mode')) {
const height = chatInputContainer.offsetHeight;
document.documentElement.style.setProperty('--chat-input-height', height + 'px');
}
});
})();

View File

@ -0,0 +1,513 @@
/**
* 对话管理器 - 负责管理聊天会话的保存、加载和持久化
*/
class ChatManager {
constructor() {
this.currentChatId = null;
this.chats = new Map(); // 存储所有聊天会话
this.storageKey = 'yundage_chat_history';
this.currentUserKey = 'yundage_current_user';
this.initialized = false;
// 监听认证状态变化事件
window.addEventListener('authStatusChecked', () => {
if (!this.initialized) {
console.log('ChatManager 收到认证状态检查完成事件,开始初始化');
this.init();
this.initialized = true;
}
});
// 备用方案如果3秒内没有收到认证事件强制初始化
setTimeout(() => {
if (!this.initialized) {
console.log('ChatManager 超时初始化');
this.init();
this.initialized = true;
}
}, 3000);
}
/**
* 初始化
*/
init() {
this.loadFromStorage();
this.setupAutoSave();
}
/**
* 从本地存储加载聊天记录
*/
loadFromStorage() {
try {
// 清空当前聊天记录
this.chats.clear();
this.currentChatId = null;
const currentUser = this.getCurrentUser();
if (!currentUser) {
console.log('没有登录用户,跳过加载');
return;
}
const storageKey = `${this.storageKey}_${currentUser.id}`;
const currentChatKey = `${this.storageKey}_current_${currentUser.id}`;
console.log(`[ChatManager] 开始加载聊天记录...`);
console.log(`[ChatManager] 存储键: ${storageKey}`);
console.log(`[ChatManager] 当前聊天键: ${currentChatKey}`);
const savedChats = localStorage.getItem(storageKey);
const savedCurrentChatId = localStorage.getItem(currentChatKey);
console.log(`[ChatManager] localStorage中保存的当前聊天ID: ${savedCurrentChatId}`);
if (savedChats) {
const chatsData = JSON.parse(savedChats);
this.chats = new Map(chatsData.map(chat => [chat.id, chat]));
console.log(`[ChatManager] 成功加载了 ${this.chats.size} 个聊天会话`);
console.log(`[ChatManager] 聊天ID列表:`, Array.from(this.chats.keys()));
// 恢复当前聊天ID
if (savedCurrentChatId && this.chats.has(savedCurrentChatId)) {
this.currentChatId = savedCurrentChatId;
console.log(`[ChatManager] ✅ 成功恢复当前聊天: ${savedCurrentChatId}`);
} else if (savedCurrentChatId) {
console.log(`[ChatManager] ⚠️ 保存的聊天ID ${savedCurrentChatId} 不在聊天列表中`);
} else {
console.log(`[ChatManager] 没有保存的当前聊天ID`);
}
} else {
console.log(`[ChatManager] 未找到保存的聊天记录`);
}
console.log(`[ChatManager] 加载完成当前聊天ID: ${this.currentChatId}`);
} catch (error) {
console.error('[ChatManager] 加载聊天记录失败:', error);
}
}
/**
* 保存到本地存储
*/
saveToStorage() {
try {
const currentUser = this.getCurrentUser();
if (!currentUser) {
console.log('没有登录用户,跳过保存');
return;
}
const storageKey = `${this.storageKey}_${currentUser.id}`;
const chatsArray = Array.from(this.chats.values());
console.log(`正在保存 ${chatsArray.length} 个聊天到 localStorage键名: ${storageKey}`);
localStorage.setItem(storageKey, JSON.stringify(chatsArray));
console.log(`聊天记录已保存 (用户: ${currentUser.username}, ID: ${currentUser.id})`);
} catch (error) {
console.error('保存聊天记录失败:', error);
}
}
/**
* 获取当前用户
*/
getCurrentUser() {
const userStr = localStorage.getItem(this.currentUserKey);
return userStr ? JSON.parse(userStr) : null;
}
/**
* 设置自动保存
*/
setupAutoSave() {
// 每30秒自动保存一次
setInterval(() => {
if (this.currentChatId) {
this.saveToStorage();
}
}, 30000);
// 页面卸载时保存
window.addEventListener('beforeunload', () => {
this.saveToStorage();
});
}
/**
* 创建新的聊天会话
* @param {string} firstMessage - 第一条消息,用于生成标题
* @returns {string} 聊天ID
*/
createChat(firstMessage = '') {
const chatId = `chat_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const chat = {
id: chatId,
title: this.generateTitle(firstMessage),
messages: [],
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
metadata: {
messageCount: 0,
lastActive: new Date().toISOString()
}
};
this.chats.set(chatId, chat);
this.currentChatId = chatId;
// 保存当前聊天ID
const currentUser = this.getCurrentUser();
if (currentUser) {
const currentChatKey = `${this.storageKey}_current_${currentUser.id}`;
localStorage.setItem(currentChatKey, chatId);
}
this.saveToStorage();
return chatId;
}
/**
* 生成聊天标题
* @param {string} firstMessage - 第一条消息
* @returns {string} 标题
*/
generateTitle(firstMessage) {
if (!firstMessage) return '新对话';
// 移除多余的空格和换行
const cleaned = firstMessage.trim().replace(/\s+/g, ' ');
// 如果消息太长截取前30个字符
if (cleaned.length > 30) {
return cleaned.substring(0, 30) + '...';
}
return cleaned || '新对话';
}
/**
* 添加消息到当前聊天
* @param {string} content - 消息内容
* @param {string} type - 消息类型 ('user' 或 'ai')
* @param {object} metadata - 额外的元数据
*/
addMessage(content, type, metadata = {}) {
if (!this.currentChatId) {
console.error('没有活动的聊天会话');
return;
}
const chat = this.chats.get(this.currentChatId);
if (!chat) {
console.error('聊天会话不存在');
return;
}
const message = {
id: `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
content,
contentType: metadata.contentType || 'markdown', // 默认使用markdown
type,
timestamp: new Date().toISOString(),
status: type === 'user' ? 'sent' : 'complete',
metadata: {
deviceInfo: navigator.userAgent,
...metadata
}
};
chat.messages.push(message);
chat.updatedAt = new Date().toISOString();
chat.metadata.messageCount = chat.messages.length;
chat.metadata.lastActive = new Date().toISOString();
// 如果是第一条消息,更新标题
if (chat.messages.length === 1 && type === 'user') {
chat.title = this.generateTitle(content);
}
this.chats.set(this.currentChatId, chat);
// 触发消息添加事件
this.dispatchEvent('messageAdded', { chatId: this.currentChatId, message });
// 保存到本地存储
this.saveToStorage();
return message;
}
/**
* 获取聊天会话
* @param {string} chatId - 聊天ID
* @returns {object|null} 聊天会话
*/
getChat(chatId) {
return this.chats.get(chatId) || null;
}
/**
* 获取当前聊天会话
* @returns {object|null} 当前聊天会话
*/
getCurrentChat() {
return this.currentChatId ? this.getChat(this.currentChatId) : null;
}
/**
* 切换到指定聊天
* @param {string} chatId - 聊天ID
*/
switchToChat(chatId) {
if (this.chats.has(chatId)) {
this.currentChatId = chatId;
const chat = this.chats.get(chatId);
chat.metadata.lastActive = new Date().toISOString();
// 保存当前聊天ID
const currentUser = this.getCurrentUser();
if (currentUser) {
const currentChatKey = `${this.storageKey}_current_${currentUser.id}`;
localStorage.setItem(currentChatKey, chatId);
console.log(`保存当前聊天ID: ${chatId}`);
}
// 触发聊天切换事件
this.dispatchEvent('chatSwitched', { chatId, chat });
return true;
}
return false;
}
/**
* 删除聊天会话
* @param {string} chatId - 聊天ID
*/
deleteChat(chatId) {
if (this.chats.has(chatId)) {
this.chats.delete(chatId);
// 如果删除的是当前聊天清空当前聊天ID
if (this.currentChatId === chatId) {
this.currentChatId = null;
// 清除保存的当前聊天ID
const currentUser = this.getCurrentUser();
if (currentUser) {
const currentChatKey = `${this.storageKey}_current_${currentUser.id}`;
localStorage.removeItem(currentChatKey);
}
}
this.saveToStorage();
// 触发聊天删除事件
this.dispatchEvent('chatDeleted', { chatId });
return true;
}
return false;
}
/**
* 更新聊天标题
* @param {string} chatId - 聊天ID
* @param {string} newTitle - 新标题
*/
updateChatTitle(chatId, newTitle) {
const chat = this.chats.get(chatId);
if (chat) {
chat.title = newTitle;
chat.updatedAt = new Date().toISOString();
this.chats.set(chatId, chat);
this.saveToStorage();
// 触发标题更新事件
this.dispatchEvent('chatTitleUpdated', { chatId, title: newTitle });
return true;
}
return false;
}
/**
* 获取所有聊天列表
* @param {object} options - 选项
* @returns {array} 聊天列表
*/
getAllChats(options = {}) {
const { sortBy = 'updatedAt', order = 'desc', limit = null } = options;
let chatsArray = Array.from(this.chats.values());
// 排序
chatsArray.sort((a, b) => {
const aValue = a[sortBy];
const bValue = b[sortBy];
if (order === 'desc') {
return bValue > aValue ? 1 : -1;
} else {
return aValue > bValue ? 1 : -1;
}
});
// 限制数量
if (limit) {
chatsArray = chatsArray.slice(0, limit);
}
return chatsArray;
}
/**
* 搜索聊天记录
* @param {string} query - 搜索关键词
* @returns {array} 匹配的聊天列表
*/
searchChats(query) {
const lowerQuery = query.toLowerCase();
const results = [];
for (const chat of this.chats.values()) {
// 搜索标题
if (chat.title.toLowerCase().includes(lowerQuery)) {
results.push({
chat,
matchType: 'title'
});
continue;
}
// 搜索消息内容
const matchedMessages = chat.messages.filter(msg =>
msg.content.toLowerCase().includes(lowerQuery)
);
if (matchedMessages.length > 0) {
results.push({
chat,
matchType: 'content',
matchedMessages
});
}
}
return results;
}
/**
* 清空所有聊天记录
*/
clearAllChats() {
this.chats.clear();
this.currentChatId = null;
this.saveToStorage();
// 触发清空事件
this.dispatchEvent('allChatsCleared');
}
/**
* 导出聊天记录
* @param {string} chatId - 聊天ID如果不提供则导出所有
* @returns {string} JSON格式的聊天记录
*/
exportChats(chatId = null) {
if (chatId) {
const chat = this.chats.get(chatId);
return chat ? JSON.stringify(chat, null, 2) : null;
} else {
const allChats = Array.from(this.chats.values());
return JSON.stringify(allChats, null, 2);
}
}
/**
* 导入聊天记录
* @param {string} jsonData - JSON格式的聊天记录
*/
importChats(jsonData) {
try {
const data = JSON.parse(jsonData);
const chatsToImport = Array.isArray(data) ? data : [data];
for (const chat of chatsToImport) {
if (chat.id && chat.messages) {
this.chats.set(chat.id, chat);
}
}
this.saveToStorage();
// 触发导入事件
this.dispatchEvent('chatsImported', { count: chatsToImport.length });
return true;
} catch (error) {
console.error('导入聊天记录失败:', error);
return false;
}
}
/**
* 获取聊天统计信息
*/
getStatistics() {
const totalChats = this.chats.size;
let totalMessages = 0;
let userMessages = 0;
let aiMessages = 0;
for (const chat of this.chats.values()) {
totalMessages += chat.messages.length;
userMessages += chat.messages.filter(m => m.type === 'user').length;
aiMessages += chat.messages.filter(m => m.type === 'ai').length;
}
return {
totalChats,
totalMessages,
userMessages,
aiMessages,
averageMessagesPerChat: totalChats > 0 ? Math.round(totalMessages / totalChats) : 0
};
}
/**
* 触发自定义事件
* @param {string} eventName - 事件名称
* @param {object} detail - 事件详情
*/
dispatchEvent(eventName, detail = {}) {
const event = new CustomEvent(`chatManager:${eventName}`, { detail });
window.dispatchEvent(event);
}
/**
* 开始新的聊天会话
*/
startNewChat() {
this.currentChatId = null;
// 清除保存的当前聊天ID
const currentUser = this.getCurrentUser();
if (currentUser) {
const currentChatKey = `${this.storageKey}_current_${currentUser.id}`;
localStorage.removeItem(currentChatKey);
}
this.dispatchEvent('newChatStarted');
}
/**
* 用户登录后重新加载聊天记录
*/
reloadForUser() {
console.log('用户登录状态变化,重新加载聊天记录');
this.loadFromStorage();
this.dispatchEvent('userChanged');
}
}
// 创建全局实例
window.chatManager = new ChatManager();

View File

@ -0,0 +1,2 @@
[ZoneTransfer]
ZoneId=3

View File

@ -0,0 +1,468 @@
/**
* 聊天模式性能优化
* 在聊天模式下禁用轮播和动画以节省资源
*/
(function() {
'use strict';
let animationsPaused = false;
let carouselInterval = null;
let originalAnimations = new Map();
/**
* 暂停所有CSS动画
*/
function pauseCSSAnimations() {
// 获取所有有动画的元素
const animatedElements = document.querySelectorAll('*');
animatedElements.forEach(element => {
const computedStyle = window.getComputedStyle(element);
const animationName = computedStyle.animationName;
const animationDuration = computedStyle.animationDuration;
if (animationName && animationName !== 'none') {
// 保存原始动画设置
originalAnimations.set(element, {
animationName: animationName,
animationDuration: animationDuration,
animationPlayState: computedStyle.animationPlayState
});
// 暂停动画
element.style.animationPlayState = 'paused';
}
});
}
/**
* 恢复所有CSS动画
*/
function resumeCSSAnimations() {
originalAnimations.forEach((styles, element) => {
element.style.animationPlayState = styles.animationPlayState || 'running';
});
originalAnimations.clear();
}
/**
* 停止轮播
*/
function stopCarousel() {
// 停止ticker轮播
const tickerCarousel = document.getElementById('tickerCarousel');
if (tickerCarousel) {
tickerCarousel.style.animationPlayState = 'paused';
console.log('已暂停ticker轮播');
}
// 停止所有自定义轮播动画
const carouselElements = document.querySelectorAll('.ticker-carousel, .news-carousel');
carouselElements.forEach(el => {
el.style.animationPlayState = 'paused';
});
// 查找并停止所有轮播相关的定时器
if (window.tickerInterval) {
clearInterval(window.tickerInterval);
window.tickerInterval = null;
}
// 停止新闻轮播定时器
if (window.newsRotationInterval) {
clearInterval(window.newsRotationInterval);
window.newsRotationInterval = null;
console.log('已停止新闻轮播定时器');
}
// 保存所有活动的定时器ID以便恢复
window.pausedIntervals = [];
// 暴力停止所有定时器(作为后备方案)
const highestId = window.setTimeout(function() {
for (let i = highestId; i >= 0; i--) {
window.clearInterval(i);
}
}, 0);
// 停止Bootstrap轮播
const carousels = document.querySelectorAll('.carousel');
carousels.forEach(carousel => {
if (typeof $ !== 'undefined' && $(carousel).carousel) {
$(carousel).carousel('pause');
}
});
// 停止Swiper轮播
if (window.Swiper) {
const swipers = document.querySelectorAll('.swiper-container');
swipers.forEach(swiperEl => {
if (swiperEl.swiper) {
swiperEl.swiper.autoplay.stop();
}
});
}
}
/**
* 恢复轮播
*/
function startCarousel() {
// 恢复Bootstrap轮播
const carousels = document.querySelectorAll('.carousel');
carousels.forEach(carousel => {
if ($(carousel).carousel) {
$(carousel).carousel('cycle');
}
});
// 恢复Swiper轮播
if (window.Swiper) {
const swipers = document.querySelectorAll('.swiper-container');
swipers.forEach(swiperEl => {
if (swiperEl.swiper) {
swiperEl.swiper.autoplay.start();
}
});
}
}
/**
* 禁用背景视频
*/
function pauseBackgroundVideos() {
const videos = document.querySelectorAll('video');
videos.forEach(video => {
if (!video.paused) {
video.pause();
video.dataset.wasPaused = 'false';
} else {
video.dataset.wasPaused = 'true';
}
});
}
/**
* 恢复背景视频
*/
function resumeBackgroundVideos() {
const videos = document.querySelectorAll('video');
videos.forEach(video => {
if (video.dataset.wasPaused === 'false') {
video.play();
}
});
}
/**
* 进入聊天模式时的优化
*/
function optimizeForChatMode() {
if (animationsPaused) return;
console.log('进入聊天模式,暂停动画和轮播');
// 添加聊天模式激活类
document.body.classList.add('chat-mode-active');
// 冻结背景渐变但保持可见
const animatedGradient = document.querySelector('.animated-gradient');
if (animatedGradient) {
// 获取当前背景并冻结
const currentStyle = window.getComputedStyle(animatedGradient);
const currentBackground = currentStyle.backgroundImage || currentStyle.background;
// 保存原始样式
animatedGradient.dataset.originalBackground = animatedGradient.style.background;
animatedGradient.dataset.originalTransition = animatedGradient.style.transition;
// 设置固定背景
animatedGradient.style.background = currentBackground;
animatedGradient.style.transition = 'none';
// 停止requestAnimationFrame动画
// 设置标志位阻止动画继续
window.pauseBackgroundAnimation = true;
console.log('已冻结背景渐变动画,保持当前颜色');
}
// 暂停CSS动画但不包括背景和功能性元素
const animatedElements = document.querySelectorAll('[style*="animation"]');
animatedElements.forEach(el => {
// 不处理背景渐变、加载动画和工具按钮
if (!el.classList.contains('animated-gradient') &&
!el.classList.contains('loading-spinner') &&
el.id !== 'chatModeLoadingSpinner' &&
el.id !== 'loadingSpinner' &&
!el.closest('#toolsMenuBtn')) {
el.style.animationPlayState = 'paused';
}
});
// 停止轮播
stopCarousel();
// 暂停背景视频
pauseBackgroundVideos();
// 只隐藏3D动画元素
const tickerElements = document.querySelectorAll(
'.ticker-pulse-ring, .ticker-carousel, .news-carousel, .ticker-flip-card'
);
tickerElements.forEach(el => {
el.style.display = 'none';
});
// 隐藏整个ticker区域
const tickerSection = document.querySelector('.ticker-section');
if (tickerSection) {
tickerSection.style.display = 'none';
console.log('已隐藏3D ticker区域');
}
animationsPaused = true;
}
/**
* 退出聊天模式时恢复
*/
function restoreFromChatMode() {
if (!animationsPaused) return;
console.log('退出聊天模式,恢复动画和轮播');
// 移除聊天模式激活类
document.body.classList.remove('chat-mode-active');
// 恢复背景渐变动画
const animatedGradient = document.querySelector('.animated-gradient');
if (animatedGradient) {
// 恢复原始样式
animatedGradient.style.background = animatedGradient.dataset.originalBackground || '';
animatedGradient.style.transition = animatedGradient.dataset.originalTransition || '';
delete animatedGradient.dataset.originalBackground;
delete animatedGradient.dataset.originalTransition;
// 恢复背景动画
window.pauseBackgroundAnimation = false;
// 重新启动背景动画如果有initBackground函数
if (window.initBackground && typeof window.initBackground === 'function') {
window.initBackground();
}
console.log('已恢复背景渐变动画');
}
// 恢复CSS动画
const animatedElements = document.querySelectorAll('[style*="animation"]');
animatedElements.forEach(el => {
if (el.id !== 'chatModeLoadingSpinner' && el.id !== 'loadingSpinner') {
el.style.animationPlayState = '';
}
});
// 恢复轮播
startCarousel();
// 恢复背景视频
resumeBackgroundVideos();
// 恢复3D动画元素
const tickerElements = document.querySelectorAll(
'.ticker-pulse-ring, .ticker-carousel, .news-carousel, .ticker-flip-card'
);
tickerElements.forEach(el => {
el.style.display = '';
});
// 恢复ticker区域
const tickerSection = document.querySelector('.ticker-section');
if (tickerSection) {
tickerSection.style.display = '';
console.log('已恢复3D ticker区域');
}
// 重新启动新闻轮播
if (window.initTicker3D && typeof window.initTicker3D === 'function') {
window.initTicker3D();
}
animationsPaused = false;
}
/**
* 检测聊天模式状态
*/
function checkChatModeStatus() {
const chatContainer = document.getElementById('chatContainer');
const welcomeScreen = document.getElementById('welcomeScreen');
// 判断是否在聊天模式
const isInChatMode = chatContainer && chatContainer.style.display !== 'none' &&
(!welcomeScreen || welcomeScreen.style.display === 'none');
if (isInChatMode) {
optimizeForChatMode();
} else {
restoreFromChatMode();
}
}
/**
* 监听模式切换
*/
function setupModeObserver() {
// 监听聊天容器的显示状态变化
const chatContainer = document.getElementById('chatContainer');
const welcomeScreen = document.getElementById('welcomeScreen');
if (!chatContainer) {
setTimeout(setupModeObserver, 500);
return;
}
// 创建MutationObserver监听display变化
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'attributes' &&
(mutation.attributeName === 'style' || mutation.attributeName === 'class')) {
checkChatModeStatus();
}
});
});
// 开始观察
if (chatContainer) {
observer.observe(chatContainer, {
attributes: true,
attributeFilter: ['style', 'class']
});
}
if (welcomeScreen) {
observer.observe(welcomeScreen, {
attributes: true,
attributeFilter: ['style', 'class']
});
}
// 初始检查
checkChatModeStatus();
}
/**
* 添加全局样式优化
*/
function addOptimizationStyles() {
const style = document.createElement('style');
style.id = 'chat-mode-optimization';
style.textContent = `
/* 保持背景但停止过渡动画 */
.chat-mode-active .animated-gradient {
transition: none !important;
}
/* 隐藏3D ticker区域 */
.chat-mode-active .ticker-section {
display: none !important;
}
/* 隐藏脉冲环动画 */
.chat-mode-active .ticker-pulse-ring {
display: none !important;
}
/* 隐藏3D卡片和轮播 */
.chat-mode-active .ticker-carousel,
.chat-mode-active .ticker-flip-card,
.chat-mode-active .news-carousel {
display: none !important;
}
/* 聊天模式下隐藏轮播控制器 */
.chat-mode-active .carousel-control-prev,
.chat-mode-active .carousel-control-next,
.chat-mode-active .carousel-indicators {
display: none !important;
}
/* 确保spin动画关键帧存在 */
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* 保持加载动画正常运行 */
.chat-mode-active .loading-spinner,
.chat-mode-active #loadingSpinner,
.chat-mode-active #chatModeLoadingSpinner {
animation: spin 1s linear infinite !important;
animation-play-state: running !important;
}
/* 只停止装饰性动画,不影响功能性动画 */
.chat-mode-active .ticker-pulse-ring,
.chat-mode-active .wave-animation {
animation-play-state: paused !important;
}
/* 减少GPU使用 */
.chat-mode-active .ticker-3d,
.chat-mode-active .particles-js,
.chat-mode-active .wave-animation {
transform: none !important;
will-change: auto !important;
}
`;
document.head.appendChild(style);
}
/**
* 初始化
*/
function init() {
// 添加优化样式
addOptimizationStyles();
// DOM加载完成后设置监听
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', setupModeObserver);
} else {
setupModeObserver();
}
// 监听页面可见性变化
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
// 页面不可见时暂停所有动画
optimizeForChatMode();
} else {
// 页面可见时根据模式恢复
checkChatModeStatus();
}
});
// 为body添加聊天模式类
const originalSendMessage = window.sendMessage;
if (originalSendMessage) {
window.sendMessage = function() {
document.body.classList.add('chat-mode-active');
return originalSendMessage.apply(this, arguments);
};
}
}
// 启动
init();
// 导出API供外部调用
window.chatModeOptimizer = {
pause: optimizeForChatMode,
resume: restoreFromChatMode,
check: checkChatModeStatus
};
})();

View File

@ -0,0 +1,155 @@
/**
* 强制文本换行处理
* 解决列表项中文本不换行的问题
*/
(function() {
'use strict';
/**
* 处理列表项的换行
*/
function processListItems() {
// 获取所有消息中的列表项
const listItems = document.querySelectorAll(
'.ai-message li, .user-message li, .message li, #chatMessages li'
);
listItems.forEach(li => {
// 获取列表项的计算样式
const computedStyle = window.getComputedStyle(li);
const parentWidth = li.parentElement.offsetWidth;
// 如果列表项内容超出父容器宽度,强制设置样式
if (li.scrollWidth > parentWidth) {
li.style.wordBreak = 'break-all';
li.style.overflowWrap = 'anywhere';
li.style.whiteSpace = 'normal';
li.style.maxWidth = '100%';
}
// 处理列表项中的所有文本节点
const walker = document.createTreeWalker(
li,
NodeFilter.SHOW_TEXT,
null,
false
);
let textNode;
while (textNode = walker.nextNode()) {
const text = textNode.textContent;
// 检查是否包含长连续字符串超过30个字符没有空格
if (text && /\S{30,}/.test(text)) {
// 在长字符串中插入零宽空格以允许换行
const newText = text.replace(/(\S{20})/g, '$1\u200B');
if (newText !== text) {
textNode.textContent = newText;
}
}
}
});
}
/**
* 设置全局样式
*/
function setGlobalStyles() {
// 检查是否已经添加了样式
if (document.getElementById('force-wrap-styles')) {
return;
}
// 创建style元素
const style = document.createElement('style');
style.id = 'force-wrap-styles';
style.textContent = `
/* 强制列表项换行 */
.ai-message li,
.user-message li,
.message li,
#chatMessages li {
word-break: break-all !important;
overflow-wrap: anywhere !important;
white-space: normal !important;
max-width: 100% !important;
line-break: anywhere !important;
}
/* 列表容器设置 */
.ai-message ul,
.ai-message ol,
.user-message ul,
.user-message ol,
.message ul,
.message ol,
#chatMessages ul,
#chatMessages ol {
max-width: 100% !important;
width: 100% !important;
}
`;
// 添加到head
document.head.appendChild(style);
}
/**
* 监听DOM变化
*/
function setupObserver() {
const chatContainer = document.getElementById('chatMessages');
if (!chatContainer) {
setTimeout(setupObserver, 500);
return;
}
// 创建观察器
const observer = new MutationObserver((mutations) => {
// 当有新消息添加时处理
let hasNewMessages = false;
mutations.forEach((mutation) => {
if (mutation.addedNodes.length > 0) {
hasNewMessages = true;
}
});
if (hasNewMessages) {
// 延迟处理,等待渲染完成
setTimeout(processListItems, 100);
}
});
// 开始观察
observer.observe(chatContainer, {
childList: true,
subtree: true
});
}
/**
* 初始化
*/
function init() {
// 设置全局样式
setGlobalStyles();
// DOM加载完成后执行
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
processListItems();
setupObserver();
});
} else {
processListItems();
setupObserver();
}
// 定期检查(后备方案)
setInterval(processListItems, 3000);
}
// 启动
init();
})();

View File

@ -0,0 +1,2 @@
[ZoneTransfer]
ZoneId=3

View File

@ -0,0 +1,2 @@
[ZoneTransfer]
ZoneId=3

2852
AIEC-server/js/main.js Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,2 @@
[ZoneTransfer]
ZoneId=3

View File

@ -0,0 +1,2 @@
[ZoneTransfer]
ZoneId=3

View File

@ -0,0 +1,418 @@
/**
* Markdown渲染器 - 处理消息的Markdown格式化显示
*/
class MarkdownRenderer {
constructor() {
this.isMarkdownMode = true; // 默认开启Markdown模式
this.codeBlockId = 0; // 用于生成唯一的代码块ID
this.init();
}
init() {
// 加载用户偏好设置
const savedMode = localStorage.getItem('yundage_markdown_mode');
if (savedMode !== null) {
this.isMarkdownMode = savedMode === 'true';
}
}
/**
* 切换Markdown模式
*/
toggleMarkdownMode() {
this.isMarkdownMode = !this.isMarkdownMode;
localStorage.setItem('yundage_markdown_mode', this.isMarkdownMode);
return this.isMarkdownMode;
}
/**
* 渲染Markdown内容
* @param {string} content - 原始Markdown内容
* @param {object} options - 渲染选项
* @returns {string} HTML内容
*/
render(content, options = {}) {
if (!this.isMarkdownMode) {
// 纯文本模式,只转换换行
return this.escapeHtml(content).replace(/\n/g, '<br>');
}
// 先强制替换所有数字序列为符号(有序列表)
content = content.replace(/^\d+\.\s+/gm, '▶ ');
// Markdown模式渲染
let html = content;
// 1. 处理代码块(```语言\n代码\n```
html = this.renderCodeBlocks(html);
// 2. 处理行内代码(`代码`
html = this.renderInlineCode(html);
// 3. 处理标题(# ## ### 等)
html = this.renderHeaders(html);
// 4. 处理粗体(**文字** 或 __文字__
html = this.renderBold(html);
// 5. 处理斜体(*文字* 或 _文字_
html = this.renderItalic(html);
// 6. 处理链接([文字](链接)
html = this.renderLinks(html);
// 7. 处理图片(![alt](src)
html = this.renderImages(html);
// 8. 处理列表
html = this.renderLists(html);
// 9. 处理引用(> 引用内容)
html = this.renderBlockquotes(html);
// 10. 处理表格
html = this.renderTables(html);
// 11. 处理分隔线(--- 或 ***
html = this.renderHorizontalRules(html);
// 12. 处理换行
html = this.renderLineBreaks(html);
// 13. 最后强制处理所有数字序列,替换为符号
html = html.replace(/^\d+\.\s+/gm, '▸ ');
return html;
}
/**
* 转义HTML特殊字符
*/
escapeHtml(text) {
const map = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
};
return text.replace(/[&<>"']/g, m => map[m]);
}
/**
* 渲染代码块
*/
renderCodeBlocks(text) {
const codeBlockRegex = /```(\w+)?\n([\s\S]*?)```/g;
return text.replace(codeBlockRegex, (match, language, code) => {
const id = `code-block-${this.codeBlockId++}`;
const escapedCode = this.escapeHtml(code.trim());
const lang = language || 'plaintext';
return `
<div class="code-block-wrapper mb-4">
<div class="code-header flex justify-between items-center bg-gray-700 text-gray-200 px-3 py-1 rounded-t text-sm">
<span class="code-language">${lang}</span>
<button class="copy-code-btn hover:bg-gray-600 px-2 py-1 rounded transition-colors" data-code-id="${id}">
<i class="fas fa-copy"></i> 复制
</button>
</div>
<pre class="code-block bg-gray-800 text-gray-100 p-4 rounded-b overflow-x-auto" id="${id}"><code class="language-${lang}">${escapedCode}</code></pre>
</div>
`;
});
}
/**
* 渲染行内代码
*/
renderInlineCode(text) {
return text.replace(/`([^`]+)`/g, '<code class="inline-code bg-gray-200 text-red-600 px-1 py-0.5 rounded text-sm">$1</code>');
}
/**
* 渲染标题
*/
renderHeaders(text) {
// H6 到 H1从小到大避免误匹配
for (let i = 6; i >= 1; i--) {
const regex = new RegExp(`^${'#'.repeat(i)}\\s+(.+)$`, 'gm');
text = text.replace(regex, `<h${i} class="text-${this.getHeaderSize(i)} font-bold mb-3 mt-4">$1</h${i}>`);
}
return text;
}
getHeaderSize(level) {
const sizes = ['3xl', '2xl', 'xl', 'lg', 'base', 'sm'];
return sizes[level - 1] || 'base';
}
/**
* 渲染粗体
*/
renderBold(text) {
return text
.replace(/\*\*([^*]+)\*\*/g, '<strong class="font-bold">$1</strong>')
.replace(/__([^_]+)__/g, '<strong class="font-bold">$1</strong>');
}
/**
* 渲染斜体
*/
renderItalic(text) {
return text
.replace(/\*([^*]+)\*/g, '<em class="italic">$1</em>')
.replace(/_([^_]+)_/g, '<em class="italic">$1</em>');
}
/**
* 渲染链接
*/
renderLinks(text) {
return text.replace(/\[([^\]]+)\]\(([^)]+)\)/g,
'<a href="$2" target="_blank" class="text-blue-600 hover:underline">$1</a>');
}
/**
* 渲染图片
*/
renderImages(text) {
return text.replace(/!\[([^\]]*)\]\(([^)]+)\)/g,
'<img src="$2" alt="$1" class="max-w-full rounded shadow-md my-2">');
}
/**
* 渲染列表
*/
renderLists(text) {
// 简化处理直接将所有列表项包装为HTML不添加额外符号
// 无序列表
text = text.replace(/^[\*\-\+]\s+(.+)$/gm, '<li class="ml-4">$1</li>');
// 有序列表(已经在前面被替换为▶符号了)
text = text.replace(/^▶\s+(.+)$/gm, '<li class="ml-4">$1</li>');
// 统一包装所有列表项
text = text.replace(/(<li.*<\/li>\n?)+/g, match => {
return `<ul class="list-none mb-3">${match}</ul>`;
});
return text;
}
/**
* 渲染引用
*/
renderBlockquotes(text) {
return text.replace(/^>\s+(.+)$/gm,
'<blockquote class="border-l-4 border-gray-300 pl-4 py-2 my-2 text-gray-600">$1</blockquote>');
}
/**
* 渲染表格
*/
renderTables(text) {
const tableRegex = /\|(.+)\|\n\|[\s\-\|]+\|\n((?:\|.+\|\n?)+)/g;
return text.replace(tableRegex, (match, header, body) => {
const headers = header.split('|').filter(h => h.trim());
const rows = body.trim().split('\n').map(row =>
row.split('|').filter(cell => cell.trim())
);
let tableHtml = '<table class="min-w-full border-collapse border border-gray-300 my-4">';
tableHtml += '<thead><tr class="bg-gray-100">';
headers.forEach(h => {
tableHtml += `<th class="border border-gray-300 px-4 py-2 text-left">${h.trim()}</th>`;
});
tableHtml += '</tr></thead><tbody>';
rows.forEach(row => {
tableHtml += '<tr class="hover:bg-gray-50">';
row.forEach(cell => {
tableHtml += `<td class="border border-gray-300 px-4 py-2">${cell.trim()}</td>`;
});
tableHtml += '</tr>';
});
tableHtml += '</tbody></table>';
return tableHtml;
});
}
/**
* 渲染分隔线
*/
renderHorizontalRules(text) {
return text.replace(/^[\-\*]{3,}$/gm, '<hr class="my-4 border-gray-300">');
}
/**
* 渲染换行
*/
renderLineBreaks(text) {
return text.replace(/\n/g, '<br>');
}
/**
* 为代码块添加复制功能
*/
initCodeCopyButtons() {
document.addEventListener('click', (e) => {
if (e.target.closest('.copy-code-btn')) {
const btn = e.target.closest('.copy-code-btn');
const codeId = btn.dataset.codeId;
const codeBlock = document.getElementById(codeId);
if (codeBlock) {
const code = codeBlock.textContent;
navigator.clipboard.writeText(code).then(() => {
const originalHtml = btn.innerHTML;
btn.innerHTML = '<i class="fas fa-check"></i> 已复制';
setTimeout(() => {
btn.innerHTML = originalHtml;
}, 2000);
});
}
}
});
}
/**
* 创建Markdown工具栏
*/
createToolbar() {
return `
<button class="toolbar-btn" data-action="bold" title="粗体">
<i class="fas fa-bold"></i>
</button>
<button class="toolbar-btn" data-action="italic" title="斜体">
<i class="fas fa-italic"></i>
</button>
<button class="toolbar-btn" data-action="code" title="代码">
<i class="fas fa-code"></i>
</button>
<button class="toolbar-btn" data-action="link" title="链接">
<i class="fas fa-link"></i>
</button>
<button class="toolbar-btn" data-action="list" title="列表">
<i class="fas fa-list"></i>
</button>
<button class="toolbar-btn" data-action="quote" title="引用">
<i class="fas fa-quote-right"></i>
</button>
<button class="toolbar-btn toggle-markdown-btn" data-action="toggle-markdown" title="切换Markdown模式" style="margin-left: auto;">
<i class="fas fa-markdown"></i>
<span class="text-xs" style="margin-left: 0.25rem;">${this.isMarkdownMode ? 'Markdown: ON' : 'Markdown: OFF'}</span>
</button>
`;
}
/**
* 处理工具栏操作
*/
handleToolbarAction(action, textarea) {
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const text = textarea.value;
const selectedText = text.substring(start, end);
let replacement = '';
let cursorOffset = 0;
switch (action) {
case 'bold':
replacement = `**${selectedText || '粗体文字'}**`;
cursorOffset = selectedText ? replacement.length : 2;
break;
case 'italic':
replacement = `*${selectedText || '斜体文字'}*`;
cursorOffset = selectedText ? replacement.length : 1;
break;
case 'code':
if (selectedText.includes('\n')) {
replacement = `\`\`\`\n${selectedText}\n\`\`\``;
cursorOffset = 4;
} else {
replacement = `\`${selectedText || '代码'}\``;
cursorOffset = selectedText ? replacement.length : 1;
}
break;
case 'link':
replacement = `[${selectedText || '链接文字'}](URL)`;
cursorOffset = selectedText ? replacement.length - 4 : 1;
break;
case 'list':
replacement = `- ${selectedText || '列表项'}`;
cursorOffset = 2;
break;
case 'quote':
replacement = `> ${selectedText || '引用文字'}`;
cursorOffset = 2;
break;
}
textarea.value = text.substring(0, start) + replacement + text.substring(end);
textarea.selectionStart = start + cursorOffset;
textarea.selectionEnd = start + cursorOffset;
textarea.focus();
}
// 新增:为工具栏绑定事件
attachToolbarEvents(toolbar, textarea) {
toolbar.addEventListener('click', (e) => {
const button = e.target.closest('button[data-action]');
if (button) {
const action = button.dataset.action;
if (action === 'toggle-markdown') {
this.toggleMarkdownMode();
button.textContent = this.isMarkdownMode ? 'Markdown: ON' : 'Markdown: OFF';
} else {
this.insertMarkdown(textarea, action);
}
}
});
}
}
// 创建全局实例
window.markdownRenderer = new MarkdownRenderer();
// 初始化工具栏函数
function initMarkdownToolbars() {
console.log('初始化Markdown工具栏...');
// 为欢迎模式输入框创建工具栏
const welcomeToolbar = document.getElementById('markdownToolbar');
const welcomeInput = document.getElementById('messageInput');
if (welcomeToolbar && welcomeInput) {
console.log('创建欢迎模式工具栏');
welcomeToolbar.innerHTML = window.markdownRenderer.createToolbar();
welcomeToolbar.style.display = 'flex';
window.markdownRenderer.attachToolbarEvents(welcomeToolbar, welcomeInput);
}
// 为聊天模式输入框创建工具栏
const chatToolbar = document.getElementById('chatModeMarkdownToolbar');
const chatInput = document.getElementById('chatModeMessageInput');
if (chatToolbar && chatInput) {
console.log('创建聊天模式工具栏');
chatToolbar.innerHTML = window.markdownRenderer.createToolbar();
chatToolbar.style.display = 'flex';
window.markdownRenderer.attachToolbarEvents(chatToolbar, chatInput);
}
}
// 初始化工具栏
document.addEventListener('DOMContentLoaded', function() {
initMarkdownToolbars();
});
// 确保在所有资源加载完成后也初始化一次
window.addEventListener('load', function() {
setTimeout(initMarkdownToolbars, 100);
});
// 导出初始化函数供其他模块使用
window.initMarkdownToolbars = initMarkdownToolbars;

View File

@ -0,0 +1,2 @@
[ZoneTransfer]
ZoneId=3

View File

@ -0,0 +1,143 @@
/**
* 强制移除聊天消息中的所有br标签
* 用于修复列表项中的过多间距问题
*/
(function() {
'use strict';
/**
* 移除指定元素内的所有br标签
*/
function removeBrTags(element) {
if (!element) return;
// 查找所有br标签
const brTags = element.querySelectorAll('br');
brTags.forEach(br => {
// 完全移除br标签
br.remove();
});
}
/**
* 处理聊天消息
*/
function processChatMessages() {
// 获取聊天消息容器
const chatContainer = document.getElementById('chatMessages');
if (!chatContainer) return;
// 移除容器内的所有br标签
removeBrTags(chatContainer);
// 针对特定的消息类
const messageSelectors = [
'.message',
'.message-item',
'.ai-message',
'.user-message',
'.message-content'
];
messageSelectors.forEach(selector => {
const elements = chatContainer.querySelectorAll(selector);
elements.forEach(element => {
removeBrTags(element);
});
});
// 特别处理列表项
const listItems = chatContainer.querySelectorAll('li');
listItems.forEach(li => {
removeBrTags(li);
// 确保列表项内的段落不会产生额外间距
const paragraphs = li.querySelectorAll('p');
paragraphs.forEach(p => {
// 如果段落只包含文本将其内容直接放到li中
if (p.childNodes.length === 1 && p.childNodes[0].nodeType === Node.TEXT_NODE) {
const text = p.textContent;
const textNode = document.createTextNode(text);
p.parentNode.replaceChild(textNode, p);
}
});
});
}
/**
* 创建MutationObserver监听新消息
*/
function setupObserver() {
const chatContainer = document.getElementById('chatMessages');
if (!chatContainer) {
// 如果容器还不存在,稍后重试
setTimeout(setupObserver, 500);
return;
}
// 创建观察器
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
// 检查是否有新节点添加
if (mutation.addedNodes.length > 0) {
mutation.addedNodes.forEach(node => {
if (node.nodeType === Node.ELEMENT_NODE) {
// 处理新添加的元素
removeBrTags(node);
// 如果是消息元素,进行额外处理
if (node.classList && (
node.classList.contains('message') ||
node.classList.contains('message-item') ||
node.classList.contains('ai-message') ||
node.classList.contains('user-message')
)) {
// 处理其中的列表项
const listItems = node.querySelectorAll('li');
listItems.forEach(li => {
removeBrTags(li);
});
}
}
});
}
});
});
// 配置观察器
const config = {
childList: true,
subtree: true
};
// 开始观察
observer.observe(chatContainer, config);
// 立即处理现有消息
processChatMessages();
}
/**
* 初始化
*/
function init() {
// DOM加载完成后执行
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
setupObserver();
processChatMessages();
});
} else {
setupObserver();
processChatMessages();
}
// 定期检查(作为后备方案)
setInterval(processChatMessages, 2000);
}
// 启动
init();
})();

View File

@ -0,0 +1,468 @@
/**
* 流式状态显示组件
* 用于显示RAG检索过程的实时状态
*/
class StreamStatusDisplay {
constructor(container) {
this.container = container;
this.statusElements = {};
this.retrievalHistory = []; // 记录检索历史
this.statusHistory = []; // 记录所有状态历史
this.maxDisplayItems = 3; // 最多显示3条状态
this.eventQueue = []; // 事件队列
this.isProcessing = false; // 是否正在处理队列
this.minDisplayTime = 500; // 每个状态最少显示500ms
this.init();
}
init() {
// 创建状态显示区域
this.statusArea = document.createElement('div');
this.statusArea.className = 'stream-status-area';
this.statusArea.style.cssText = `
padding: 12px 16px;
background: linear-gradient(135deg, #f6f9fc 0%, #f0f4f8 100%);
border-radius: 12px;
margin-bottom: 16px;
font-size: 14px;
color: #475569;
min-height: 48px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
display: flex;
align-items: center;
gap: 12px;
position: relative;
overflow: hidden;
`;
// 添加动画背景
const animBg = document.createElement('div');
animBg.style.cssText = `
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(99, 102, 241, 0.1), transparent);
animation: shimmer 2s infinite;
`;
this.statusArea.appendChild(animBg);
// 添加CSS动画
if (!document.getElementById('stream-status-styles')) {
const style = document.createElement('style');
style.id = 'stream-status-styles';
style.textContent = `
@keyframes shimmer {
0% { left: -100%; }
100% { left: 100%; }
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
@keyframes rotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.status-icon {
font-size: 20px;
animation: pulse 1.5s ease-in-out infinite;
}
.status-spinner {
animation: rotate 1s linear infinite;
}
`;
document.head.appendChild(style);
}
// 状态内容容器
this.contentArea = document.createElement('div');
this.contentArea.style.cssText = `
position: relative;
z-index: 1;
flex: 1;
display: flex;
align-items: center;
gap: 12px;
`;
this.statusArea.appendChild(this.contentArea);
this.container.appendChild(this.statusArea);
}
updateStatus(event) {
// 清除淡出定时器
if (this.fadeTimeout) {
clearTimeout(this.fadeTimeout);
}
switch (event.type) {
case 'cache_hit':
case 'cache':
if (event.cached) {
this.showCacheHit();
} else {
this.showCacheMiss();
}
break;
case 'cache_miss':
this.showCacheMiss();
break;
case 'starting':
this.showStatus('🔍 开始分析查询...', '#6366f1');
break;
case 'complexity_check':
const complexity = event.data;
if (complexity) {
if (complexity.is_complex) {
this.showStatus(
`📊 复杂查询 (${complexity.level || '高'}) - 置信度: ${(complexity.confidence * 100).toFixed(1)}%`,
'#f59e0b'
);
} else {
this.showStatus(
`📊 简单查询 - 置信度: ${(complexity.confidence * 100).toFixed(1)}%`,
'#10b981'
);
}
}
break;
case 'documents':
const docs = event.data;
if (docs) {
// 记录检索历史
this.retrievalHistory.push({
count: docs.count,
new_docs: docs.new_docs || 0,
is_incremental: docs.is_incremental || false,
retrieval_type: docs.retrieval_type || '检索',
retrieval_reason: docs.retrieval_reason || ''
});
// 根据是否是增量检索显示不同信息
let message = '';
if (docs.is_incremental) {
// 增量检索(第二次及以后)
const reason = docs.retrieval_reason || '继续检索';
message = `📚 ${reason}:新增 ${docs.new_docs || 0} 篇文档(总计 ${docs.count} 篇)`;
this.showStatus(message, '#8b5cf6'); // 紫色表示增量
} else {
// 初始检索
message = `📚 已检索 ${docs.count} 篇文档`;
if (docs.retrieval_type) {
message = `📚 ${docs.retrieval_type}${docs.count} 篇文档`;
}
this.showStatus(message, '#6366f1'); // 蓝色表示初始
}
// 显示检索历史汇总
if (this.retrievalHistory.length > 1) {
this.showRetrievalSummary();
}
// 显示来源文档
if (docs.sources && docs.sources.length > 0) {
this.addSources(docs.sources);
}
}
break;
case 'sufficiency_check':
const sufficient = event.data;
if (sufficient) {
// 置信度可能是数字或未定义
const confidence = sufficient.confidence !== undefined ? sufficient.confidence : 0.5;
if (sufficient.is_sufficient) {
this.showStatus(
`✅ 信息充分 (置信度: ${(confidence * 100).toFixed(1)}%)`,
'#10b981'
);
} else {
this.showStatus(
`⚠️ 信息不足,继续检索... (置信度: ${(confidence * 100).toFixed(1)}%)`,
'#f59e0b'
);
}
}
break;
case 'sub_queries':
const queries = event.data;
if (queries && queries.length > 0) {
this.showStatus(`🔄 生成了 ${queries.length} 个子查询`, '#8b5cf6');
this.showSubQueries(queries);
}
break;
case 'iteration':
const iter = event.data;
if (iter) {
// 只显示当前轮次,不显示最大轮次
this.showStatus(`🔄 第 ${iter.current} 轮迭代`, '#6366f1');
}
break;
case 'generating':
// 不要在这里调用scheduleFadeOut
this.showStatus('✨ 正在生成答案...', '#f59e0b'); // 使用黄色表示进行中
break;
case 'cached':
this.showStatus('💾 结果已缓存', '#10b981');
this.scheduleFadeOut();
break;
case 'complete':
this.showStatus('✅ 答案生成完成', '#10b981');
// 答案生成完成后2秒后自动消失
setTimeout(() => {
this.fadeOutAndHide();
}, 2000);
break;
case 'cancelled':
// 清除之前的所有状态
this.statusHistory = [];
// 显示取消状态
this.showStatus('⚠️ 任务已停止', '#ef4444');
// 1.5秒后淡出
setTimeout(() => {
this.fadeOutAndHide();
}, 1500);
break;
default:
if (event.message) {
this.showStatus(event.message, '#6366f1');
}
}
}
showStatus(message, color = '#6366f1') {
// 添加到历史记录
this.statusHistory.push({
message: message,
color: color,
timestamp: Date.now()
});
// 使用独立的渲染方法
this.renderStatuses();
}
showCacheHit() {
this.statusArea.style.background = 'linear-gradient(135deg, #dcfce7 0%, #d1fae5 100%)';
this.statusArea.style.borderLeft = '3px solid #10b981';
this.contentArea.innerHTML = `
<div class="status-icon" style="color: #10b981;">📦</div>
<div class="status-message" style="color: #065f46; font-weight: 500;">
使用缓存结果(伪流式输出)
</div>
`;
}
showCacheMiss() {
this.statusArea.style.background = 'linear-gradient(135deg, #fef3c7 0%, #fde68a 100%)';
this.statusArea.style.borderLeft = '3px solid #f59e0b';
this.contentArea.innerHTML = `
<div class="status-icon status-spinner" style="color: #f59e0b;">🔍</div>
<div class="status-message" style="color: #78350f; font-weight: 500;">
开始实时检索...
</div>
`;
}
showSubQueries(queries) {
const maxDisplay = 3;
const displayQueries = queries.slice(0, maxDisplay);
const remaining = queries.length - maxDisplay;
// 构建子查询列表HTML
const queriesList = displayQueries.map((q, i) =>
`${i + 1}. ${q.length > 30 ? q.substring(0, 30) + '...' : q}`
).join('<br>');
const moreText = remaining > 0 ? `<br>... 还有 ${remaining}` : '';
// 更新最后一个状态的消息,添加子查询列表
if (this.statusHistory.length > 0) {
const lastStatus = this.statusHistory[this.statusHistory.length - 1];
// 如果消息中还没有子查询列表,就添加
if (!lastStatus.message.includes('<br>')) {
lastStatus.message += `<br><span style="color: #4f46e5; font-size: 12px; margin-left: 32px;">${queriesList}${moreText}</span>`;
// 重新渲染
this.renderStatuses();
}
}
}
showRetrievalSummary() {
// 显示检索历史汇总
const totalRounds = this.retrievalHistory.length;
const totalDocs = this.retrievalHistory[this.retrievalHistory.length - 1].count;
// 创建一个小的汇总显示
const summaryHtml = `
<span style="
display: inline-block;
margin-left: 12px;
padding: 2px 8px;
background: rgba(99, 102, 241, 0.1);
border-radius: 4px;
font-size: 12px;
color: #6366f1;
">共 ${totalRounds} 轮检索</span>
`;
// 添加到当前状态显示中
if (!this.contentArea.querySelector('.retrieval-summary')) {
const summarySpan = document.createElement('span');
summarySpan.className = 'retrieval-summary';
summarySpan.innerHTML = summaryHtml;
this.contentArea.appendChild(summarySpan);
} else {
this.contentArea.querySelector('.retrieval-summary').innerHTML = summaryHtml;
}
}
addSources(sources) {
// 不单独添加,而是将来源信息合并到最新的状态消息中
const maxDisplay = 3;
const displaySources = sources.slice(0, maxDisplay);
const sourceText = displaySources.join(', ');
const moreText = sources.length > maxDisplay ? `${sources.length}` : '';
// 更新最后一个状态的消息,添加来源信息
if (this.statusHistory.length > 0) {
const lastStatus = this.statusHistory[this.statusHistory.length - 1];
// 如果消息中还没有来源信息,就添加
if (!lastStatus.message.includes('来源:')) {
lastStatus.message += `<br><span style="color: #64748b; font-size: 12px; margin-left: 32px;">来源: ${sourceText}${moreText}</span>`;
// 重新渲染
this.renderStatuses();
}
}
}
renderStatuses() {
// 抽取渲染逻辑为独立方法
const recentStatuses = this.statusHistory.slice(-this.maxDisplayItems);
this.contentArea.innerHTML = `
<div style="display: flex; flex-direction: column; gap: 8px;">
${recentStatuses.map((status, index) => {
const isLatest = index === recentStatuses.length - 1;
const opacity = isLatest ? 1 : 0.6;
return `
<div style="
display: flex;
align-items: center;
gap: 12px;
opacity: ${opacity};
padding: ${isLatest ? '8px 0' : '4px 0'};
border-bottom: ${!isLatest ? '1px solid rgba(226, 232, 240, 0.5)' : 'none'};
">
<div class="status-icon" style="color: ${status.color}; font-size: ${isLatest ? '20px' : '16px'};">⚡</div>
<div class="status-message" style="
color: #334155;
font-weight: ${isLatest ? '500' : '400'};
font-size: ${isLatest ? '14px' : '12px'};
">
${status.message}
</div>
</div>
`;
}).join('')}
</div>
`;
// 更新背景色
if (recentStatuses.length > 0) {
const latestStatus = recentStatuses[recentStatuses.length - 1];
this.statusArea.style.background = `linear-gradient(135deg, ${this.hexToRgba(latestStatus.color, 0.05)} 0%, ${this.hexToRgba(latestStatus.color, 0.1)} 100%)`;
this.statusArea.style.borderLeft = `3px solid ${latestStatus.color}`;
}
}
scheduleFadeOut() {
// 清除之前的定时器
if (this.fadeTimeout) {
clearTimeout(this.fadeTimeout);
}
// 5秒后淡出
this.fadeTimeout = setTimeout(() => {
this.fadeOut();
}, 5000);
}
fadeOut() {
this.statusArea.style.opacity = '0.3';
this.statusArea.style.transform = 'scale(0.98)';
// 不完全隐藏,保留淡化状态
// setTimeout(() => {
// this.statusArea.style.display = 'none';
// }, 300);
}
fadeOutAndHide() {
// 添加过渡动画
this.statusArea.style.transition = 'all 0.5s ease-out';
this.statusArea.style.opacity = '0';
this.statusArea.style.transform = 'scale(0.95)';
this.statusArea.style.maxHeight = this.statusArea.offsetHeight + 'px';
// 动画结束后完全隐藏
setTimeout(() => {
this.statusArea.style.maxHeight = '0';
this.statusArea.style.padding = '0';
this.statusArea.style.margin = '0';
this.statusArea.style.overflow = 'hidden';
// 再过渡一段时间后完全移除显示
setTimeout(() => {
this.statusArea.style.display = 'none';
}, 500);
}, 500);
}
clear() {
if (this.fadeTimeout) {
clearTimeout(this.fadeTimeout);
}
this.contentArea.innerHTML = '';
this.statusArea.style.opacity = '1';
this.statusArea.style.transform = 'scale(1)';
this.statusArea.style.display = 'flex';
this.statusArea.style.background = 'linear-gradient(135deg, #f6f9fc 0%, #f0f4f8 100%)';
this.statusArea.style.borderLeft = 'none';
// 重置历史记录
this.retrievalHistory = [];
this.statusHistory = [];
}
destroy() {
if (this.fadeTimeout) {
clearTimeout(this.fadeTimeout);
}
if (this.statusArea && this.statusArea.parentNode) {
this.statusArea.parentNode.removeChild(this.statusArea);
}
}
hexToRgba(hex, alpha) {
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
}
}
// 导出
window.StreamStatusDisplay = StreamStatusDisplay;
console.log('[StreamStatusDisplay] 组件已加载');

View File

@ -0,0 +1,154 @@
/**
* 开关按钮与原有逻辑的桥接
* 保持原有的事件处理逻辑不变
*/
(function() {
'use strict';
// DOM加载完成后初始化
document.addEventListener('DOMContentLoaded', function() {
// 获取欢迎模式开关元素
const deepResearchToggle = document.getElementById('deepResearchToggle');
const showThinkingToggle = document.getElementById('showThinkingToggle');
// 获取聊天模式开关元素
const chatModeDeepResearchToggle = document.getElementById('chatModeDeepResearchToggle');
const chatModeShowThinkingToggle = document.getElementById('chatModeShowThinkingToggle');
// 获取原有的菜单项元素
const deepResearchItem = document.getElementById('deepResearchItem');
const showThinkingItem = document.getElementById('showThinkingItem');
const chatModeDeepResearchItem = document.getElementById('chatModeDeepResearchItem');
const chatModeShowThinkingItem = document.getElementById('chatModeShowThinkingItem');
// 获取状态显示元素
const deepResearchStatus = document.getElementById('deepResearchStatus');
const showThinkingStatus = document.getElementById('showThinkingStatus');
const chatModeDeepResearchStatus = document.getElementById('chatModeDeepResearchStatus');
const chatModeShowThinkingStatus = document.getElementById('chatModeShowThinkingStatus');
// 初始化开关状态为关闭
if (deepResearchToggle) {
deepResearchToggle.checked = false;
}
if (showThinkingToggle) {
showThinkingToggle.checked = false;
}
if (chatModeDeepResearchToggle) {
chatModeDeepResearchToggle.checked = false;
}
if (chatModeShowThinkingToggle) {
chatModeShowThinkingToggle.checked = false;
}
// 深度研究开关处理
if (deepResearchToggle && deepResearchItem) {
// 标记避免循环触发
let isUpdatingFromToggle = false;
deepResearchToggle.addEventListener('change', function() {
if (isUpdatingFromToggle) return;
// 设置标记
isUpdatingFromToggle = true;
// 触发原有的点击事件
deepResearchItem.click();
// 延迟重置标记
setTimeout(() => {
isUpdatingFromToggle = false;
}, 100);
});
// 监听原有逻辑的状态变化
const observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.type === 'childList' || mutation.type === 'characterData') {
const status = deepResearchStatus ? deepResearchStatus.textContent : '关';
deepResearchToggle.checked = status === '开';
// 同步到聊天模式的开关
if (chatModeDeepResearchToggle) {
chatModeDeepResearchToggle.checked = status === '开';
}
}
});
});
if (deepResearchStatus) {
observer.observe(deepResearchStatus, { childList: true, characterData: true, subtree: true });
}
}
// 显示思考开关处理 - 已禁用
if (showThinkingToggle) {
// 禁用开关,不添加任何事件监听
showThinkingToggle.disabled = true;
showThinkingToggle.checked = false; // 始终保持关闭状态
}
// 聊天模式深度研究开关处理
if (chatModeDeepResearchToggle && chatModeDeepResearchItem) {
// 标记避免循环触发
let isUpdatingFromToggle = false;
chatModeDeepResearchToggle.addEventListener('change', function() {
if (isUpdatingFromToggle) return;
// 设置标记
isUpdatingFromToggle = true;
// 触发原有的点击事件
chatModeDeepResearchItem.click();
// 延迟重置标记
setTimeout(() => {
isUpdatingFromToggle = false;
}, 100);
});
// 监听原有逻辑的状态变化
const observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.type === 'childList' || mutation.type === 'characterData') {
const status = chatModeDeepResearchStatus ? chatModeDeepResearchStatus.textContent : '关';
chatModeDeepResearchToggle.checked = status === '开';
// 同步两个模式的开关状态
if (deepResearchToggle) {
deepResearchToggle.checked = status === '开';
}
}
});
});
if (chatModeDeepResearchStatus) {
observer.observe(chatModeDeepResearchStatus, { childList: true, characterData: true, subtree: true });
}
}
// 聊天模式显示思考开关处理 - 已禁用
if (chatModeShowThinkingToggle) {
// 禁用开关,不添加任何事件监听
chatModeShowThinkingToggle.disabled = true;
chatModeShowThinkingToggle.checked = false; // 始终保持关闭状态
}
// 阻止菜单项本身的点击事件冒泡到开关
[deepResearchItem, showThinkingItem, chatModeDeepResearchItem, chatModeShowThinkingItem].forEach(item => {
if (item) {
const originalClick = item.onclick;
item.onclick = function(e) {
// 如果点击的是开关,不执行原有逻辑
if (e.target.closest('.switch')) {
return;
}
// 否则执行原有逻辑
if (originalClick) {
originalClick.call(this, e);
}
};
}
});
});
})();

View File

@ -0,0 +1,373 @@
/**
* 视口和缩放处理器
* 处理页面缩放、视口变化和响应式布局
*/
class ViewportHandler {
constructor() {
this.currentZoom = 1;
this.breakpoints = {
xs: 320,
sm: 640,
md: 768,
lg: 1024,
xl: 1280,
'2xl': 1536
};
this.init();
}
init() {
// 检测初始缩放级别
this.detectZoomLevel();
// 设置事件监听器
this.setupEventListeners();
// 应用初始视口设置
this.applyViewportSettings();
// 处理初始布局
this.handleViewportChange();
}
/**
* 检测浏览器缩放级别
*/
detectZoomLevel() {
// 方法1使用 window.devicePixelRatio
const pixelRatio = window.devicePixelRatio || 1;
// 方法2使用 outerWidth 和 innerWidth 比较
const zoomLevel = Math.round((window.outerWidth / window.innerWidth) * 100) / 100;
// 方法3使用媒体查询检测
const mqString = `(resolution: ${window.devicePixelRatio}dppx)`;
const mq = window.matchMedia(mqString);
this.currentZoom = pixelRatio;
this.updateZoomClasses();
}
/**
* 设置事件监听器
*/
setupEventListeners() {
// 监听窗口大小变化
let resizeTimer;
window.addEventListener('resize', () => {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(() => {
this.handleViewportChange();
this.detectZoomLevel();
}, 250);
});
// 监听缩放变化
window.addEventListener('wheel', (e) => {
if (e.ctrlKey || e.metaKey) {
setTimeout(() => {
this.detectZoomLevel();
}, 100);
}
});
// 监听方向变化(移动设备)
window.addEventListener('orientationchange', () => {
setTimeout(() => {
this.handleViewportChange();
}, 500);
});
// 监听媒体查询变化
this.setupMediaQueryListeners();
}
/**
* 设置媒体查询监听器
*/
setupMediaQueryListeners() {
// 监听不同断点
Object.entries(this.breakpoints).forEach(([name, width]) => {
const mq = window.matchMedia(`(min-width: ${width}px)`);
mq.addListener((e) => {
if (e.matches) {
this.onBreakpointChange(name, width);
}
});
});
// 监听高DPI屏幕
const highDpiQuery = window.matchMedia('(-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi)');
highDpiQuery.addListener((e) => {
if (e.matches) {
document.body.classList.add('high-dpi');
} else {
document.body.classList.remove('high-dpi');
}
});
// 监听触摸设备
const touchQuery = window.matchMedia('(hover: none) and (pointer: coarse)');
touchQuery.addListener((e) => {
if (e.matches) {
document.body.classList.add('touch-device');
} else {
document.body.classList.remove('touch-device');
}
});
}
/**
* 处理视口变化
*/
handleViewportChange() {
const viewport = {
width: window.innerWidth,
height: window.innerHeight,
orientation: window.innerWidth > window.innerHeight ? 'landscape' : 'portrait',
zoom: this.currentZoom
};
// 更新CSS变量
this.updateCSSVariables(viewport);
// 调整布局
this.adjustLayout(viewport);
// 优化字体大小
this.optimizeFontSize(viewport);
// 处理特殊组件
this.handleSpecialComponents(viewport);
// 触发自定义事件
window.dispatchEvent(new CustomEvent('viewportChanged', { detail: viewport }));
}
/**
* 更新CSS变量
*/
updateCSSVariables(viewport) {
const root = document.documentElement;
// 视口尺寸
root.style.setProperty('--vw', `${viewport.width * 0.01}px`);
root.style.setProperty('--vh', `${viewport.height * 0.01}px`);
root.style.setProperty('--vmin', `${Math.min(viewport.width, viewport.height) * 0.01}px`);
root.style.setProperty('--vmax', `${Math.max(viewport.width, viewport.height) * 0.01}px`);
// 缩放相关
root.style.setProperty('--zoom-level', this.currentZoom);
root.style.setProperty('--base-font-size', `${16 / this.currentZoom}px`);
// 响应式间距
const spacingUnit = Math.max(4, Math.min(8, viewport.width / 200));
root.style.setProperty('--spacing-unit', `${spacingUnit}px`);
}
/**
* 更新缩放相关的CSS类
*/
updateZoomClasses() {
const body = document.body;
// 移除旧的缩放类
body.classList.remove('zoom-50', 'zoom-75', 'zoom-90', 'zoom-100', 'zoom-110', 'zoom-125', 'zoom-150', 'zoom-200');
// 添加新的缩放类
if (this.currentZoom <= 0.5) {
body.classList.add('zoom-50');
} else if (this.currentZoom <= 0.75) {
body.classList.add('zoom-75');
} else if (this.currentZoom <= 0.9) {
body.classList.add('zoom-90');
} else if (this.currentZoom <= 1.1) {
body.classList.add('zoom-100');
} else if (this.currentZoom <= 1.25) {
body.classList.add('zoom-110');
} else if (this.currentZoom <= 1.5) {
body.classList.add('zoom-125');
} else if (this.currentZoom <= 2) {
body.classList.add('zoom-150');
} else {
body.classList.add('zoom-200');
}
}
/**
* 调整布局
*/
adjustLayout(viewport) {
const sidebar = document.querySelector('.sidebar');
const mainContent = document.querySelector('.main-content');
// 小屏幕自动折叠侧边栏
if (viewport.width < 768 && sidebar && !sidebar.classList.contains('collapsed')) {
// 触发侧边栏折叠
const toggleBtn = document.getElementById('toggleSidebar');
if (toggleBtn) {
toggleBtn.click();
}
}
// 调整聊天消息容器高度
const chatMessages = document.getElementById('chatMessages');
if (chatMessages) {
const headerHeight = document.querySelector('header')?.offsetHeight || 0;
const inputHeight = document.getElementById('chatModeInput')?.offsetHeight || 0;
const availableHeight = viewport.height - headerHeight - inputHeight - 40; // 40px for padding
chatMessages.style.maxHeight = `${availableHeight}px`;
}
}
/**
* 优化字体大小
*/
optimizeFontSize(viewport) {
// 根据视口宽度计算基础字体大小
let baseFontSize = 16;
if (viewport.width < 360) {
baseFontSize = 14;
} else if (viewport.width < 768) {
baseFontSize = 15;
} else if (viewport.width > 1920) {
baseFontSize = 18;
}
// 应用缩放调整
baseFontSize = baseFontSize / this.currentZoom;
// 设置根字体大小
document.documentElement.style.fontSize = `${baseFontSize}px`;
}
/**
* 处理特殊组件
*/
handleSpecialComponents(viewport) {
// 3D新闻卡片调整
const ticker3D = document.querySelector('.ticker-3d-container');
const tickerSection = document.querySelector('.ticker-section');
if (ticker3D) {
// 根据缩放级别调整3D卡片
if (this.currentZoom > 1.5) {
// 高缩放时隐藏
if (tickerSection) tickerSection.style.display = 'none';
} else if (this.currentZoom > 1.25) {
// 中等缩放时缩小
ticker3D.style.transform = 'scale(0.7)';
ticker3D.style.maxHeight = '80px';
if (tickerSection) tickerSection.style.display = 'block';
} else if (viewport.width < 768) {
ticker3D.style.transform = 'scale(0.8)';
if (tickerSection) tickerSection.style.display = 'block';
} else if (viewport.width < 1024) {
ticker3D.style.transform = 'scale(0.9)';
if (tickerSection) tickerSection.style.display = 'block';
} else {
ticker3D.style.transform = 'scale(1)';
ticker3D.style.maxHeight = '';
if (tickerSection) tickerSection.style.display = 'block';
}
// 3D卡片现在固定在底部不需要检测重叠
}
// 文字轮播调整
const rotatingWord = document.querySelector('.rotating-word-3d');
if (rotatingWord) {
if (viewport.width < 480) {
rotatingWord.style.fontSize = '1rem';
} else if (viewport.width < 768) {
rotatingWord.style.fontSize = '1.25rem';
} else {
rotatingWord.style.fontSize = '';
}
}
}
/**
* 应用视口设置
*/
applyViewportSettings() {
// 设置视口meta标签
let viewportMeta = document.querySelector('meta[name="viewport"]');
if (!viewportMeta) {
viewportMeta = document.createElement('meta');
viewportMeta.name = 'viewport';
document.head.appendChild(viewportMeta);
}
// 根据设备类型设置不同的视口配置
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
if (isMobile) {
viewportMeta.content = 'width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=yes';
} else {
viewportMeta.content = 'width=device-width, initial-scale=1.0';
}
}
/**
* 断点变化处理
*/
onBreakpointChange(name, width) {
console.log(`Breakpoint changed to: ${name} (${width}px)`);
// 触发自定义事件
window.dispatchEvent(new CustomEvent('breakpointChanged', {
detail: { name, width }
}));
}
/**
* 获取当前断点
*/
getCurrentBreakpoint() {
const width = window.innerWidth;
let current = 'xs';
for (const [name, breakpoint] of Object.entries(this.breakpoints)) {
if (width >= breakpoint) {
current = name;
}
}
return current;
}
/**
* 检查是否为移动设备
*/
isMobile() {
return window.innerWidth < this.breakpoints.md ||
('ontouchstart' in window) ||
(navigator.maxTouchPoints > 0);
}
/**
* 检查是否为高DPI屏幕
*/
isHighDPI() {
return window.devicePixelRatio > 1 ||
(window.matchMedia && window.matchMedia('(-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi)').matches);
}
/**
* 强制重新计算布局
*/
forceRecalculate() {
this.detectZoomLevel();
this.handleViewportChange();
}
}
// 创建全局实例
window.viewportHandler = new ViewportHandler();
// 导出给其他模块使用
if (typeof module !== 'undefined' && module.exports) {
module.exports = ViewportHandler;
}