// AI助手 - 前端应用
// 使用智谱 GLM-4.5-Air 模型(流式输出 + 多对话管理)
const CONFIG = {
apiUrl: 'https://open.bigmodel.cn/api/paas/v4/chat/completions',
apiKey: '2259e33a1357460abe17919aaf81e73d.K44a8LPQTmFM5PKm',
model: 'glm-4.5-air',
maxTokens: 2048,
// Tavily Search API
tavilyApiUrl: 'https://api.tavily.com/search',
tavilyApiKey: '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'
};
// 数据结构
let conversations = []; // 对话列表
let currentConversation = null; // 当前对话
let isLoading = false;
// 当前页面状态
let currentPage = 'chats'; // chats | agents | profile
// 系统智能体数据(完整列表)
let systemAgents = [
// 热门智能体
{ id: 'assistant', name: '通用助手', avatar: '🤖', category: 'hot', desc: '能回答各类问题,帮助写作、分析、解答疑惑', systemPrompt: '你是一个智能助手,能够回答各类问题,帮助用户解决问题。', heat: 9500 },
{ id: 'writer', name: '写作助手', avatar: '✍️', category: 'hot', desc: '专注于文章写作、文案创作、内容润色', systemPrompt: '你是一个专业的写作助手,擅长各类文章写作、文案创作和内容润色。', heat: 8800 },
{ id: 'coder', name: '编程助手', avatar: '👨💻', category: 'hot', desc: '精通编程语言,解答技术问题,生成代码', systemPrompt: '你是一个专业的编程助手,精通各类编程语言,能够解答技术问题并生成高质量代码。', heat: 8500 },
{ id: 'translator', name: '翻译助手', avatar: '🌐', category: 'hot', desc: '多语言翻译,精准表达,文化适配', systemPrompt: '你是一个专业的翻译助手,精通多语言翻译,能够精准表达并适配文化差异。', heat: 7200 },
// 工作助手
{ id: 'assistant-work', name: '工作助手', avatar: '💼', category: 'work', desc: '职场问题解答,工作效率提升', systemPrompt: '你是一个工作助手,帮助解决职场问题,提升工作效率。', heat: 5000 },
{ id: 'ppt', name: 'PPT助手', avatar: '📊', category: 'work', desc: 'PPT内容生成,结构优化,设计建议', systemPrompt: '你是一个PPT助手,擅长PPT内容生成、结构优化和设计建议。', heat: 4500 },
{ id: 'excel', name: 'Excel助手', avatar: '📈', category: 'work', desc: 'Excel公式、数据分析、表格优化', systemPrompt: '你是一个Excel助手,精通Excel公式、数据分析和表格优化。', heat: 4000 },
// 学习助手
{ id: 'teacher', name: '学习助手', avatar: '📚', category: 'study', desc: '知识讲解,学习方法,考试辅导', systemPrompt: '你是一个学习助手,擅长知识讲解、学习方法指导和考试辅导。', heat: 5500 },
{ id: 'english', name: '英语助手', avatar: '🔤', category: 'study', desc: '英语学习,语法纠正,口语练习', systemPrompt: '你是一个英语助手,帮助英语学习、语法纠正和口语练习。', heat: 5000 },
{ id: 'math', name: '数学助手', avatar: '🔢', category: 'study', desc: '数学解题,公式推导,概念讲解', systemPrompt: '你是一个数学助手,擅长数学解题、公式推导和概念讲解。', heat: 4500 },
// 生活助手
{ id: 'health', name: '健康助手', avatar: '🏥', category: 'life', desc: '健康咨询,养生建议,运动指导', systemPrompt: '你是一个健康助手,提供健康咨询、养生建议和运动指导。', heat: 3500 },
{ id: 'travel', name: '旅行助手', avatar: '✈️', category: 'life', desc: '旅行规划,景点推荐,美食指南', systemPrompt: '你是一个旅行助手,擅长旅行规划、景点推荐和美食指南。', heat: 3200 },
{ id: 'food', name: '美食助手', avatar: '🍳', category: 'life', desc: '菜谱推荐,烹饪技巧,营养搭配', systemPrompt: '你是一个美食助手,提供菜谱推荐、烹饪技巧和营养搭配建议。', heat: 3000 },
];
// 用户智能体界面显示的智能体(默认显示热门智能体)
let agents = [];
// 用户添加的智能体(按类别分组)
let myAgents = {
hot: ['assistant', 'writer', 'coder', 'translator'],
work: ['assistant-work', 'ppt', 'excel'],
study: ['teacher', 'english', 'math'],
life: ['health', 'travel', 'food']
};
// 收藏的智能体列表
let favoriteAgents = [];
// 置顶的智能体列表(按类别)
let pinnedAgents = {
hot: [],
work: [],
study: [],
life: []
};
let currentAgent = null; // 当前选中的智能体
// 用户状态
let currentUser = null; // 当前登录用户 { username, password, registeredAt }
// 每日使用统计(未登录用户)
let dailyUsage = {
date: null, // 日期 YYYY-MM-DD
chatSessions: 0, // 对话会话数
chatMessages: 0, // 对话消息数
agentUsed: null, // 使用的智能体ID(只能用一个)
agentMessages: 0 // 智能体消息数
};
// 未登录用户限制
const GUEST_LIMITS = {
maxChatSessionsPerDay: 1, // 每天最多1个对话会话
maxChatMessagesPerDay: 20, // 每天最多20条对话消息
maxAgentPerDay: 1, // 每天只能用1个智能体(通用助手)
maxAgentMessagesPerDay: 20 // 每天最多20条智能体消息
};
// 获取今日日期字符串
function getTodayDate() {
const today = new Date();
return `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`;
}
// 检查并重置每日使用统计
function checkDailyUsage() {
const today = getTodayDate();
if (dailyUsage.date !== today) {
// 新的一天,重置统计
dailyUsage = {
date: today,
chatSessions: 0,
chatMessages: 0,
agentUsed: null,
agentMessages: 0
};
saveDailyUsage();
}
}
// 保存用户状态
function saveCurrentUser() {
if (currentUser) {
localStorage.setItem('currentUser', JSON.stringify(currentUser));
} else {
localStorage.removeItem('currentUser');
}
}
// 保存每日使用统计
function saveDailyUsage() {
localStorage.setItem('dailyUsage', JSON.stringify(dailyUsage));
}
// 检查是否可以创建新对话(未登录用户)
function canCreateNewChat() {
if (currentUser) return true; // 登录用户无限制
checkDailyUsage();
return dailyUsage.chatSessions < GUEST_LIMITS.maxChatSessionsPerDay;
}
// 检查是否可以发送对话消息(未登录用户)
function canSendChatMessage() {
if (currentUser) return true; // 登录用户无限制
checkDailyUsage();
return dailyUsage.chatMessages < GUEST_LIMITS.maxChatMessagesPerDay;
}
// 检查是否可以使用智能体(未登录用户)
function canUseAgent(agentId) {
if (currentUser) return true; // 登录用户无限制
checkDailyUsage();
// 未登录用户只能使用通用助手
if (agentId !== 'assistant') {
return { allowed: false, reason: 'guest_agent_only' };
}
// 如果今天还没用智能体,可以用
if (!dailyUsage.agentUsed) {
return { allowed: true };
}
// 如果已经用了其他智能体,不能换
if (dailyUsage.agentUsed !== agentId) {
return { allowed: false, reason: 'guest_one_agent_only' };
}
return { allowed: true };
}
// 检查是否可以发送智能体消息(未登录用户)
function canSendAgentMessage() {
if (currentUser) return true; // 登录用户无限制
checkDailyUsage();
return dailyUsage.agentMessages < GUEST_LIMITS.maxAgentMessagesPerDay;
}
// 增加对话会话计数
function incrementChatSession() {
if (!currentUser) {
checkDailyUsage();
dailyUsage.chatSessions++;
saveDailyUsage();
}
}
// 增加对话消息计数
function incrementChatMessage() {
if (!currentUser) {
checkDailyUsage();
dailyUsage.chatMessages++;
saveDailyUsage();
}
}
// 设置使用的智能体(未登录用户)
function setAgentUsed(agentId) {
if (!currentUser) {
checkDailyUsage();
if (!dailyUsage.agentUsed) {
dailyUsage.agentUsed = agentId;
saveDailyUsage();
}
}
}
// 增加智能体消息计数
function incrementAgentMessage() {
if (!currentUser) {
checkDailyUsage();
dailyUsage.agentMessages++;
saveDailyUsage();
}
}
// 获取剩余配额提示
function getRemainingQuota() {
if (currentUser) return null; // 登录用户无限制
checkDailyUsage();
const chatSessionRemain = GUEST_LIMITS.maxChatSessionsPerDay - dailyUsage.chatSessions;
const chatMsgRemain = GUEST_LIMITS.maxChatMessagesPerDay - dailyUsage.chatMessages;
const agentMsgRemain = GUEST_LIMITS.maxAgentMessagesPerDay - dailyUsage.agentMessages;
return {
chatSessionRemain,
chatMsgRemain,
agentMsgRemain,
agentUsed: dailyUsage.agentUsed
};
}
// 获取使用智能体的对话列表(按时间倒序)
function getAgentConversationHistory(limit = 5) {
return conversations
.filter(conv => conv.agentId) // 筛选有智能体的对话
.sort((a, b) => b.updatedAt - a.updatedAt) // 按更新时间倒序
.slice(0, limit)
.map(conv => {
const agent = systemAgents.find(a => a.id === conv.agentId);
return {
...conv,
agent: agent
};
});
}
// 功能开关
let enableThinking = false; // 深度思考
let enableSearch = false; // 联网搜索
let autoScrollEnabled = true; // 自动滚动(用户滚动后可关闭)
// DOM 元素(初始为 null,在 openConversation 时重新获取)
let appContainer = null;
let messagesContainer = null;
let messagesDiv = null;
let userInput = null;
let sendBtn = null;
let welcome = null;
let thinkingBtn = null;
let searchBtn = null;
// 初始化
document.addEventListener('DOMContentLoaded', () => {
// 初始化 appContainer
appContainer = document.getElementById('app');
// 从本地存储加载对话列表
const saved = localStorage.getItem('conversations');
if (saved) {
conversations = JSON.parse(saved);
}
// 兼容旧数据格式(chat_history)
const oldHistory = localStorage.getItem('chat_history');
if (oldHistory && conversations.length === 0) {
const oldMessages = JSON.parse(oldHistory);
if (oldMessages.length > 0) {
// 转换旧数据为新格式
const convertedConv = {
id: Date.now().toString(),
title: oldMessages[0].content.slice(0, 30) + (oldMessages[0].content.length > 30 ? '...' : ''),
messages: oldMessages,
createdAt: Date.now(),
updatedAt: Date.now()
};
conversations.push(convertedConv);
saveConversations();
localStorage.removeItem('chat_history'); // 清理旧数据
}
}
// 加载用户智能体数据
const savedMyAgents = localStorage.getItem('myAgents');
if (savedMyAgents) {
myAgents = JSON.parse(savedMyAgents);
}
// 加载收藏的智能体
const savedFavoriteAgents = localStorage.getItem('favoriteAgents');
if (savedFavoriteAgents) {
favoriteAgents = JSON.parse(savedFavoriteAgents);
}
// 加载置顶的智能体
const savedPinnedAgents = localStorage.getItem('pinnedAgents');
if (savedPinnedAgents) {
pinnedAgents = JSON.parse(savedPinnedAgents);
}
// 加载用户登录状态
const savedUser = localStorage.getItem('currentUser');
if (savedUser) {
currentUser = JSON.parse(savedUser);
}
// 加载每日使用统计
const savedDailyUsage = localStorage.getItem('dailyUsage');
if (savedDailyUsage) {
dailyUsage = JSON.parse(savedDailyUsage);
}
// 检查并重置每日使用统计
checkDailyUsage();
// 根据用户配置更新显示的智能体
updateAgentsDisplay();
// 加载当前页面状态
const savedPage = localStorage.getItem('currentPage');
if (savedPage) {
currentPage = savedPage;
}
// 显示主页
showMainPage();
});
// 根据用户配置更新显示的智能体
function updateAgentsDisplay() {
agents = [];
Object.keys(myAgents).forEach(category => {
myAgents[category].forEach(agentId => {
const agent = systemAgents.find(a => a.id === agentId);
if (agent) {
agents.push({
...agent,
is_pinned: pinnedAgents[category]?.includes(agentId) || false,
is_favorite: favoriteAgents.includes(agentId)
});
}
});
});
}
// 保存用户智能体配置
function saveMyAgents() {
localStorage.setItem('myAgents', JSON.stringify(myAgents));
updateAgentsDisplay();
}
// 保存收藏的智能体
function saveFavoriteAgents() {
localStorage.setItem('favoriteAgents', JSON.stringify(favoriteAgents));
}
// 保存置顶的智能体
function savePinnedAgents() {
localStorage.setItem('pinnedAgents', JSON.stringify(pinnedAgents));
}
// ==================== 主页(底部导航栏) ====================
function showMainPage() {
currentConversation = null;
currentAgent = null;
const mainHtml = `
`;
appContainer.innerHTML = mainHtml;
// 绑定底部导航事件
document.querySelectorAll('.nav-item').forEach(item => {
item.addEventListener('click', () => {
const page = item.getAttribute('data-page');
switchPage(page);
});
});
// 根据当前页面绑定其他事件
bindPageEvents();
}
// 渲染当前页面内容
function renderCurrentPage() {
switch (currentPage) {
case 'chats':
return renderChatsPage();
case 'agents':
return renderAgentsPage();
case 'profile':
return renderProfilePage();
default:
return renderChatsPage();
}
}
// 切换页面
function switchPage(page) {
currentPage = page;
localStorage.setItem('currentPage', page);
// 更新导航栏状态
document.querySelectorAll('.nav-item').forEach(item => {
item.classList.toggle('active', item.getAttribute('data-page') === page);
});
// 更新内容区
const mainContent = document.getElementById('mainContent');
if (mainContent) {
mainContent.innerHTML = renderCurrentPage();
}
// 绑定页面事件
bindPageEvents();
}
// 绑定页面事件
function bindPageEvents() {
switch (currentPage) {
case 'chats':
bindChatsPageEvents();
break;
case 'agents':
bindAgentsPageEvents();
break;
case 'profile':
bindProfilePageEvents();
break;
}
}
// ==================== 对话页面 ====================
function renderChatsPage() {
// 只显示没有智能体的普通对话
const normalConversations = conversations.filter(conv => !conv.agentId);
return `
${normalConversations.length === 0
? '
暂无对话记录
点击右上角 + 开始新对话
'
: sortNormalConversations(normalConversations).map(conv => `
${conv.is_pinned ? '
📌' : ''}
${escapeHtml(conv.title)}
${conv.messages.length} 条消息 · ${formatTime(conv.updatedAt)}
`).join('')
}
`;
}
function bindChatsPageEvents() {
const newChatBtn = document.getElementById('newChatBtn');
if (newChatBtn) {
newChatBtn.addEventListener('click', createNewConversation);
}
// 搜索功能
const searchToggleBtn = document.getElementById('searchToggleBtn');
const searchBar = document.getElementById('searchBar');
const searchInput = document.getElementById('searchInput');
const searchCloseBtn = document.getElementById('searchCloseBtn');
const searchResults = document.getElementById('searchResults');
if (searchToggleBtn) {
searchToggleBtn.addEventListener('click', () => {
if (searchBar) {
searchBar.classList.add('show');
if (searchInput) {
searchInput.focus();
}
}
});
}
if (searchCloseBtn) {
searchCloseBtn.addEventListener('click', () => {
hideSearchBarInChats();
});
}
if (searchInput) {
searchInput.addEventListener('input', (e) => {
const keyword = e.target.value.trim();
if (keyword) {
searchConversations(keyword);
} else {
if (searchResults) searchResults.innerHTML = '';
}
});
searchInput.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
hideSearchBarInChats();
}
});
}
if (searchResults) {
searchResults.addEventListener('click', (e) => {
const item = e.target.closest('.search-result-item');
if (item) {
const id = item.getAttribute('data-id');
hideSearchBarInChats();
openConversation(id);
}
});
}
function hideSearchBarInChats() {
if (searchBar) {
searchBar.classList.remove('show');
}
if (searchInput) {
searchInput.value = '';
}
if (searchResults) {
searchResults.innerHTML = '';
}
}
const conversationList = document.getElementById('conversationList');
if (conversationList) {
conversationList.addEventListener('click', (e) => {
const item = e.target.closest('.conversation-item');
if (item) {
const id = item.getAttribute('data-id');
openConversation(id);
}
});
// 长按事件
setupLongPressEvents(conversationList);
}
// 操作菜单事件
const actionMenu = document.getElementById('actionMenu');
if (actionMenu) {
actionMenu.addEventListener('click', (e) => {
const item = e.target.closest('.action-menu-item');
if (item && currentActionConvId) {
const action = item.getAttribute('data-action');
handleActionMenuAction(action, currentActionConvId);
hideActionMenu();
}
});
// 点击其他地方关闭菜单
document.addEventListener('click', (e) => {
if (actionMenu.classList.contains('show') && !actionMenu.contains(e.target)) {
hideActionMenu();
}
});
}
}
// ==================== 智能体页面 ====================
function renderAgentsPage() {
// 根据用户配置筛选智能体
const hotAgents = agents.filter(a => a.category === 'hot');
const workAgents = agents.filter(a => a.category === 'work');
const studyAgents = agents.filter(a => a.category === 'study');
const lifeAgents = agents.filter(a => a.category === 'life');
// 获取使用智能体的对话历史(最多5个)
const recentAgentConvos = getAgentConversationHistory(5);
const totalAgentConvos = conversations.filter(c => c.agentId).length;
// 是否有收藏的智能体
const hasFavorites = favoriteAgents.length > 0;
return `
${recentAgentConvos.length > 0 ? `
🕐 最近使用
${totalAgentConvos > 5 ? `
更多>>
` : ''}
${recentAgentConvos.map(conv => `
${conv.agent ? conv.agent.avatar : '🤖'}
${conv.title}
${conv.agent ? conv.agent.name : '未知智能体'}
${formatTime(conv.updatedAt)}
`).join('')}
` : ''}
${hotAgents.length > 0 ? `
🔥 热门智能体
${hotAgents.map(agent => renderAgentCard(agent)).join('')}
` : ''}
${workAgents.length > 0 ? `
💼 工作助手
${workAgents.map(agent => renderAgentCard(agent)).join('')}
` : ''}
${studyAgents.length > 0 ? `
📚 学习助手
${studyAgents.map(agent => renderAgentCard(agent)).join('')}
` : ''}
${lifeAgents.length > 0 ? `
🏠 生活助手
${lifeAgents.map(agent => renderAgentCard(agent)).join('')}
` : ''}
`;
}
// 渲染智能体卡片
function renderAgentCard(agent) {
const pinnedClass = agent.is_pinned ? 'pinned' : '';
const favoriteClass = agent.is_favorite ? 'favorite' : '';
return `
${agent.is_pinned ? '
📌' : ''}
${agent.is_favorite ? '
⭐' : ''}
${agent.avatar}
${agent.name}
${agent.desc}
`;
}
function bindAgentsPageEvents() {
// 发现智能体按钮
const agentDiscoverBtn = document.getElementById('agentDiscoverBtn');
if (agentDiscoverBtn) {
agentDiscoverBtn.addEventListener('click', () => {
showAgentDiscoverPage();
});
}
// 收藏夹按钮
const agentFavoriteBtn = document.getElementById('agentFavoriteBtn');
if (agentFavoriteBtn) {
agentFavoriteBtn.addEventListener('click', () => {
showAgentFavoritePage();
});
}
// 智能体卡片点击(新建对话)
document.querySelectorAll('.agent-card').forEach(card => {
card.addEventListener('click', (e) => {
// 如果是长按后的点击,不触发
if (card.classList.contains('long-pressed')) {
card.classList.remove('long-pressed');
return;
}
const id = card.getAttribute('data-id');
openAgent(id);
});
// 长按事件
setupAgentLongPress(card);
});
// 最近使用对话点击(打开已有对话)
document.querySelectorAll('.recent-agent-item').forEach(item => {
item.addEventListener('click', () => {
const convId = item.getAttribute('data-conv-id');
if (convId) {
openConversation(convId);
}
});
});
// 查看全部历史使用
const showAllRecentBtn = document.getElementById('showAllRecentBtn');
if (showAllRecentBtn) {
showAllRecentBtn.addEventListener('click', () => {
showAgentHistoryPage();
});
}
// 智能体操作菜单事件
const agentActionMenu = document.getElementById('agentActionMenu');
if (agentActionMenu) {
agentActionMenu.addEventListener('click', (e) => {
const item = e.target.closest('.agent-action-item');
if (item && currentActionAgentId) {
const action = item.getAttribute('data-action');
handleAgentAction(action, currentActionAgentId);
hideAgentActionMenu();
}
});
// 点击其他地方关闭菜单
document.addEventListener('click', (e) => {
if (agentActionMenu.classList.contains('show') && !agentActionMenu.contains(e.target)) {
hideAgentActionMenu();
}
});
}
}
// 当前操作的智能体ID
let currentActionAgentId = null;
// 智能体长按事件
function setupAgentLongPress(card) {
let longPressTimer = null;
card.addEventListener('touchstart', (e) => {
longPressTimer = setTimeout(() => {
currentActionAgentId = card.getAttribute('data-id');
card.classList.add('long-pressed');
showAgentActionMenu(currentActionAgentId);
}, 500);
});
card.addEventListener('touchend', () => {
if (longPressTimer) {
clearTimeout(longPressTimer);
longPressTimer = null;
}
});
card.addEventListener('touchmove', () => {
if (longPressTimer) {
clearTimeout(longPressTimer);
longPressTimer = null;
}
});
// 鼠标长按(PC端)
card.addEventListener('mousedown', (e) => {
longPressTimer = setTimeout(() => {
currentActionAgentId = card.getAttribute('data-id');
card.classList.add('long-pressed');
showAgentActionMenu(currentActionAgentId);
}, 500);
});
card.addEventListener('mouseup', () => {
if (longPressTimer) {
clearTimeout(longPressTimer);
longPressTimer = null;
}
});
card.addEventListener('mouseleave', () => {
if (longPressTimer) {
clearTimeout(longPressTimer);
longPressTimer = null;
}
});
}
// 显示智能体操作菜单
function showAgentActionMenu(agentId) {
const agentActionMenu = document.getElementById('agentActionMenu');
const agent = systemAgents.find(a => a.id === agentId);
const displayAgent = agents.find(a => a.id === agentId);
if (!agentActionMenu || !displayAgent) return;
// 更新按钮文字
const pinText = document.getElementById('agentPinText');
const favoriteText = document.getElementById('agentFavoriteText');
if (pinText) {
pinText.textContent = displayAgent.is_pinned ? '取消置顶' : '置顶';
}
if (favoriteText) {
favoriteText.textContent = displayAgent.is_favorite ? '取消收藏' : '收藏';
}
agentActionMenu.classList.add('show');
}
// 隐藏智能体操作菜单
function hideAgentActionMenu() {
const agentActionMenu = document.getElementById('agentActionMenu');
if (agentActionMenu) {
agentActionMenu.classList.remove('show');
}
currentActionAgentId = null;
}
// 处理智能体操作
function handleAgentAction(action, agentId) {
const agent = systemAgents.find(a => a.id === agentId);
if (!agent) return;
switch (action) {
case 'pin':
toggleAgentPin(agentId);
break;
case 'favorite':
toggleAgentFavorite(agentId);
break;
case 'remove':
removeAgentFromMyAgents(agentId);
break;
}
// 刷新页面
switchPage('agents');
}
// 置顶/取消置顶智能体
function toggleAgentPin(agentId) {
const agent = agents.find(a => a.id === agentId);
if (!agent) return;
const category = agent.category;
if (pinnedAgents[category]?.includes(agentId)) {
// 取消置顶
pinnedAgents[category] = pinnedAgents[category].filter(id => id !== agentId);
agent.is_pinned = false;
showToast('已取消置顶');
} else {
// 置顶
if (!pinnedAgents[category]) {
pinnedAgents[category] = [];
}
pinnedAgents[category].push(agentId);
agent.is_pinned = true;
showToast('已置顶');
}
savePinnedAgents();
saveMyAgents(); // 更新显示
}
// 收藏/取消收藏智能体
function toggleAgentFavorite(agentId) {
const agent = agents.find(a => a.id === agentId);
if (!agent) return;
if (favoriteAgents.includes(agentId)) {
// 取消收藏
favoriteAgents = favoriteAgents.filter(id => id !== agentId);
agent.is_favorite = false;
showToast('已取消收藏');
} else {
// 收藏
favoriteAgents.push(agentId);
agent.is_favorite = true;
showToast('已收藏');
}
saveFavoriteAgents();
saveMyAgents(); // 更新显示
}
// 从用户智能体列表移除
function removeAgentFromMyAgents(agentId) {
const agent = systemAgents.find(a => a.id === agentId);
if (!agent) return;
const category = agent.category;
// 从对应类别移除
if (myAgents[category]) {
myAgents[category] = myAgents[category].filter(id => id !== agentId);
}
// 同时取消置顶
if (pinnedAgents[category]) {
pinnedAgents[category] = pinnedAgents[category].filter(id => id !== agentId);
}
// 同时取消收藏
if (favoriteAgents.includes(agentId)) {
favoriteAgents = favoriteAgents.filter(id => id !== agentId);
}
saveMyAgents();
savePinnedAgents();
saveFavoriteAgents();
showToast('已移除');
}
// ==================== 智能体发现页面 ====================
function showAgentDiscoverPage() {
// 按热度排序所有系统智能体
const sortedAgents = [...systemAgents].sort((a, b) => b.heat - a.heat);
// 分类显示
const hotAgents = systemAgents.filter(a => a.category === 'hot');
const workAgents = systemAgents.filter(a => a.category === 'work');
const studyAgents = systemAgents.filter(a => a.category === 'study');
const lifeAgents = systemAgents.filter(a => a.category === 'life');
const discoverHtml = `
🔥 热门智能体
${hotAgents.map(agent => renderDiscoverCard(agent)).join('')}
💼 工作助手
${workAgents.map(agent => renderDiscoverCard(agent)).join('')}
📚 学习助手
${studyAgents.map(agent => renderDiscoverCard(agent)).join('')}
🏠 生活助手
${lifeAgents.map(agent => renderDiscoverCard(agent)).join('')}
`;
appContainer.innerHTML = discoverHtml;
// 绑定返回按钮
const backBtn = document.getElementById('discoverBackBtn');
if (backBtn) {
backBtn.addEventListener('click', () => {
showMainPage();
});
}
// 绑定搜索
const searchInput = document.getElementById('discoverSearchInput');
if (searchInput) {
searchInput.addEventListener('input', (e) => {
const keyword = e.target.value.trim().toLowerCase();
filterDiscoverAgents(keyword);
});
}
// 绑定添加/收藏按钮
bindDiscoverCardEvents();
}
// 渲染发现页面智能体卡片
function renderDiscoverCard(agent) {
const isAdded = myAgents[agent.category]?.includes(agent.id);
const isFavorite = favoriteAgents.includes(agent.id);
// 格式化热度显示
let heatDisplay = '';
if (agent.heat >= 9000) {
heatDisplay = '🔥🔥🔥';
} else if (agent.heat >= 6000) {
heatDisplay = '🔥🔥';
} else if (agent.heat >= 3000) {
heatDisplay = '🔥';
} else {
heatDisplay = `${Math.floor(agent.heat / 1000)}k`;
}
return `
${agent.desc}
`;
}
// 获取类别标签
function getCategoryLabel(category) {
const labels = {
hot: '热门',
work: '工作',
study: '学习',
life: '生活'
};
return labels[category] || category;
}
// 搜索过滤发现页面智能体
function filterDiscoverAgents(keyword) {
if (!keyword) {
// 显示所有
showDiscoverSection('hot', systemAgents.filter(a => a.category === 'hot'));
showDiscoverSection('work', systemAgents.filter(a => a.category === 'work'));
showDiscoverSection('study', systemAgents.filter(a => a.category === 'study'));
showDiscoverSection('life', systemAgents.filter(a => a.category === 'life'));
return;
}
// 搜索所有智能体
const filtered = systemAgents.filter(agent =>
agent.name.toLowerCase().includes(keyword) ||
agent.desc.toLowerCase().includes(keyword)
);
// 显示搜索结果
showDiscoverSection('hot', filtered.filter(a => a.category === 'hot'));
showDiscoverSection('work', filtered.filter(a => a.category === 'work'));
showDiscoverSection('study', filtered.filter(a => a.category === 'study'));
showDiscoverSection('life', filtered.filter(a => a.category === 'life'));
}
// 显示发现页面某个类别的智能体
function showDiscoverSection(category, agents) {
const gridId = `discover${category.charAt(0).toUpperCase() + category.slice(1)}Grid`;
const grid = document.getElementById(gridId);
if (grid) {
grid.innerHTML = agents.map(agent => renderDiscoverCard(agent)).join('');
bindDiscoverCardEvents();
}
}
// 绑定发现页面卡片按钮事件
function bindDiscoverCardEvents() {
document.querySelectorAll('.discover-action-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
const agentId = btn.getAttribute('data-id');
const action = btn.getAttribute('data-action');
if (action === 'add') {
addAgentToMyAgents(agentId);
btn.classList.toggle('added');
btn.textContent = btn.classList.contains('added') ? '已添加' : '+ 添加';
} else if (action === 'favorite') {
toggleAgentFavoriteFromDiscover(agentId);
btn.classList.toggle('favorited');
btn.textContent = btn.classList.contains('favorited') ? '★ 已收藏' : '☆ 收藏';
}
});
});
}
// 添加智能体到用户列表
function addAgentToMyAgents(agentId) {
const agent = systemAgents.find(a => a.id === agentId);
if (!agent) return;
// 检查是否已添加
if (myAgents[agent.category]?.includes(agentId)) {
showToast('已添加过该智能体');
return;
}
// 添加到对应类别
if (!myAgents[agent.category]) {
myAgents[agent.category] = [];
}
myAgents[agent.category].push(agentId);
saveMyAgents();
showToast(`已添加 ${agent.name}`);
}
// 从发现页面收藏智能体
function toggleAgentFavoriteFromDiscover(agentId) {
const agent = systemAgents.find(a => a.id === agentId);
if (!agent) return;
if (favoriteAgents.includes(agentId)) {
favoriteAgents = favoriteAgents.filter(id => id !== agentId);
showToast('已取消收藏');
} else {
favoriteAgents.push(agentId);
showToast('已收藏');
// 如果没有添加到用户列表,自动添加
if (!myAgents[agent.category]?.includes(agentId)) {
addAgentToMyAgents(agentId);
}
}
saveFavoriteAgents();
}
// ==================== 智能体收藏夹页面 ====================
function showAgentFavoritePage() {
// 获取收藏的智能体详情
const favoriteAgentsList = favoriteAgents.map(id => {
const agent = systemAgents.find(a => a.id === id);
return agent ? { ...agent, is_favorite: true } : null;
}).filter(a => a);
const favoriteHtml = `
${favoriteAgentsList.length === 0
? '
⭐
暂无收藏的智能体
点击右上角搜索按钮发现更多智能体
'
: `
${favoriteAgentsList.map(agent => `
${agent.avatar}
${agent.name}
${agent.desc}
`).join('')}
`}
`;
appContainer.innerHTML = favoriteHtml;
// 绑定返回按钮
const backBtn = document.getElementById('favoriteBackBtn');
if (backBtn) {
backBtn.addEventListener('click', () => {
showMainPage();
});
}
// 绑定智能体卡片点击(进入对话)
document.querySelectorAll('.favorite-agent-card').forEach(card => {
card.addEventListener('click', (e) => {
// 如果点击的是取消收藏按钮,不进入对话
if (e.target.classList.contains('favorite-agent-unfav')) {
return;
}
const agentId = card.getAttribute('data-id');
openAgent(agentId);
});
});
// 绑定取消收藏按钮
document.querySelectorAll('.favorite-agent-unfav').forEach(btn => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
const agentId = btn.getAttribute('data-id');
toggleAgentFavorite(agentId);
// 刷新收藏夹页面
showAgentFavoritePage();
});
});
}
// ==================== 我的页面 ====================
function renderProfilePage() {
const quota = getRemainingQuota();
return `
${currentUser ? '👤' : '👤'}
${currentUser ? currentUser.username : '游客'}
${currentUser
? `
已登录
`
: `
未登录
`
}
${!currentUser && quota ? `
今日使用配额(游客)
对话会话
${quota.chatSessionRemain}/${GUEST_LIMITS.maxChatSessionsPerDay}
对话消息
${quota.chatMsgRemain}/${GUEST_LIMITS.maxChatMessagesPerDay}
智能体消息
${quota.agentMsgRemain}/${GUEST_LIMITS.maxAgentMessagesPerDay}
可用智能体
仅通用助手 🤖
登录后解锁全部功能
` : ''}
`;
}
function bindProfilePageEvents() {
// 登录按钮
const loginBtn = document.getElementById('loginBtn');
if (loginBtn) {
loginBtn.addEventListener('click', () => {
showLoginPage();
});
}
// 注册按钮
const registerBtn = document.getElementById('registerBtn');
if (registerBtn) {
registerBtn.addEventListener('click', () => {
showRegisterPage();
});
}
// 跳过按钮(游客模式)
const skipBtn = document.getElementById('skipBtn');
if (skipBtn) {
skipBtn.addEventListener('click', () => {
showToast('以游客模式继续使用');
switchPage('chats');
});
}
// 退出登录按钮
const logoutBtn = document.getElementById('logoutBtn');
if (logoutBtn) {
logoutBtn.addEventListener('click', () => {
if (confirm('确定要退出登录吗?')) {
currentUser = null;
saveCurrentUser();
showToast('已退出登录');
switchPage('profile');
}
});
}
// 清除数据按钮
const clearDataBtn = document.getElementById('clearDataBtn');
if (clearDataBtn) {
clearDataBtn.addEventListener('click', () => {
if (confirm('确定要清除所有数据吗?这将删除所有对话记录和账户信息。')) {
localStorage.clear();
conversations = [];
currentUser = null;
dailyUsage = { date: null, chatSessions: 0, chatMessages: 0, agentUsed: null, agentMessages: 0 };
showToast('数据已清除');
showMainPage();
}
});
}
}
// ==================== 登录页面 ====================
function showLoginPage() {
const loginHtml = `
`;
appContainer.innerHTML = loginHtml;
// 绑定事件
const backBtn = document.getElementById('loginBackBtn');
if (backBtn) {
backBtn.addEventListener('click', () => {
showMainPage();
});
}
const submitBtn = document.getElementById('loginSubmitBtn');
if (submitBtn) {
submitBtn.addEventListener('click', handleLogin);
}
const goToRegister = document.getElementById('goToRegister');
if (goToRegister) {
goToRegister.addEventListener('click', () => {
showRegisterPage();
});
}
// 回车登录
document.getElementById('loginPassword')?.addEventListener('keydown', (e) => {
if (e.key === 'Enter') handleLogin();
});
}
function handleLogin() {
const username = document.getElementById('loginUsername')?.value.trim();
const password = document.getElementById('loginPassword')?.value;
if (!username) {
showToast('请输入用户名');
return;
}
if (!password) {
showToast('请输入密码');
return;
}
// 检查本地存储的用户
const users = JSON.parse(localStorage.getItem('registeredUsers') || '[]');
const user = users.find(u => u.username === username && u.password === password);
if (user) {
currentUser = { username: user.username, registeredAt: user.registeredAt };
saveCurrentUser();
showToast('登录成功');
showMainPage();
} else {
showToast('用户名或密码错误');
}
}
// ==================== 注册页面 ====================
function showRegisterPage() {
const registerHtml = `
`;
appContainer.innerHTML = registerHtml;
// 绑定事件
const backBtn = document.getElementById('registerBackBtn');
if (backBtn) {
backBtn.addEventListener('click', () => {
showMainPage();
});
}
const submitBtn = document.getElementById('registerSubmitBtn');
if (submitBtn) {
submitBtn.addEventListener('click', handleRegister);
}
const goToLogin = document.getElementById('goToLogin');
if (goToLogin) {
goToLogin.addEventListener('click', () => {
showLoginPage();
});
}
// 回车注册
document.getElementById('registerPasswordConfirm')?.addEventListener('keydown', (e) => {
if (e.key === 'Enter') handleRegister();
});
}
function handleRegister() {
const username = document.getElementById('registerUsername')?.value.trim();
const password = document.getElementById('registerPassword')?.value;
const passwordConfirm = document.getElementById('registerPasswordConfirm')?.value;
if (!username || username.length < 3 || username.length > 20) {
showToast('用户名需要3-20个字符');
return;
}
if (!password || password.length < 6 || password.length > 20) {
showToast('密码需要6-20个字符');
return;
}
if (password !== passwordConfirm) {
showToast('两次密码不一致');
return;
}
// 检查用户名是否已存在
const users = JSON.parse(localStorage.getItem('registeredUsers') || '[]');
if (users.find(u => u.username === username)) {
showToast('用户名已存在');
return;
}
// 注册新用户
const newUser = {
username,
password,
registeredAt: Date.now()
};
users.push(newUser);
localStorage.setItem('registeredUsers', JSON.stringify(users));
// 自动登录
currentUser = { username: newUser.username, registeredAt: newUser.registeredAt };
saveCurrentUser();
showToast('注册成功');
showMainPage();
}
// ==================== 限制提示 ====================
function showLimitDialog(type) {
let title = '';
let message = '';
let showLoginBtn = true;
switch (type) {
case 'chat_session':
title = '对话会话已达上限';
message = `游客每天只能创建 ${GUEST_LIMITS.maxChatSessionsPerDay} 个对话会话。\n登录后即可无限使用。`;
break;
case 'chat_message':
title = '对话消息已达上限';
message = `游客每天最多发送 ${GUEST_LIMITS.maxChatMessagesPerDay} 条对话消息。\n登录后即可无限使用。`;
break;
case 'agent_type':
title = '智能体限制';
message = `游客只能使用「通用助手」智能体。\n登录后即可使用全部智能体。`;
break;
case 'agent_change':
title = '智能体切换限制';
message = `游客每天只能使用一个智能体。\n您今天已使用过智能体,明天才能更换。\n登录后即可自由切换智能体。`;
break;
case 'agent_message':
title = '智能体消息已达上限';
message = `游客每天最多发送 ${GUEST_LIMITS.maxAgentMessagesPerDay} 条智能体消息。\n登录后即可无限使用。`;
break;
default:
title = '使用限制';
message = '登录后解锁全部功能。';
}
const dialogHtml = `
⚠️
${title}
${message}
${showLoginBtn ? '' : ''}
`;
// 添加到页面
const existingDialog = document.getElementById('limitDialog');
if (existingDialog) existingDialog.remove();
document.body.insertAdjacentHTML('beforeend', dialogHtml);
// 绑定事件
const loginBtn = document.getElementById('limitLoginBtn');
if (loginBtn) {
loginBtn.addEventListener('click', () => {
document.getElementById('limitDialog')?.remove();
showLoginPage();
});
}
const cancelBtn = document.getElementById('limitCancelBtn');
if (cancelBtn) {
cancelBtn.addEventListener('click', () => {
document.getElementById('limitDialog')?.remove();
});
}
// 点击背景关闭
document.getElementById('limitDialog')?.addEventListener('click', (e) => {
if (e.target.id === 'limitDialog') {
document.getElementById('limitDialog')?.remove();
}
});
}
// 查看全部历史使用
function showAgentHistoryPage() {
const allAgentConvos = getAgentConversationHistory(100); // 获取所有
const historyHtml = `
${allAgentConvos.length === 0
? '
暂无历史使用记录
'
: allAgentConvos.map(conv => `
${conv.agent ? conv.agent.avatar : '🤖'}
${conv.title}
${conv.agent ? conv.agent.name : '未知智能体'}
${formatTime(conv.updatedAt)}
`).join('')
}
`;
appContainer.innerHTML = historyHtml;
// 绑定返回按钮
const backBtn = document.getElementById('historyBackBtn');
if (backBtn) {
backBtn.addEventListener('click', () => {
showMainPage();
});
}
// 绑定对话点击(打开已有对话)
document.querySelectorAll('.agent-history-item').forEach(item => {
item.addEventListener('click', () => {
const convId = item.getAttribute('data-conv-id');
if (convId) {
openConversation(convId);
}
});
});
}
// 打开智能体对话
function openAgent(agentId) {
currentAgent = systemAgents.find(a => a.id === agentId);
if (!currentAgent) return;
// 检查未登录用户的智能体限制
if (!currentUser) {
const check = canUseAgent(agentId);
if (!check.allowed) {
if (check.reason === 'guest_agent_only') {
showLimitDialog('agent_type');
return;
} else if (check.reason === 'guest_one_agent_only') {
showLimitDialog('agent_change');
return;
}
}
// 记录使用的智能体
setAgentUsed(agentId);
}
// 创建新对话并设置智能体
const newConv = {
id: Date.now().toString(),
title: currentAgent.name,
messages: [],
agentId: agentId,
createdAt: Date.now(),
updatedAt: Date.now()
};
conversations.unshift(newConv);
saveConversations();
currentConversation = newConv;
// 显示对话界面
showAgentChatPage();
}
// 显示智能体对话界面
function showAgentChatPage() {
if (!currentAgent || !currentConversation) return;
const chatHtml = `
`;
appContainer.innerHTML = chatHtml;
// 重新获取 DOM 元素
messagesContainer = document.getElementById('messagesContainer');
messagesDiv = document.getElementById('messages');
userInput = document.getElementById('userInput');
sendBtn = document.getElementById('sendBtn');
welcome = document.getElementById('welcome');
thinkingBtn = document.getElementById('thinkingBtn');
searchBtn = document.getElementById('searchBtn');
// 重置自动滚动状态
autoScrollEnabled = true;
// 设置滚动监听
setupScrollListener();
// 绑定事件
const backBtn = document.getElementById('backBtn');
if (backBtn) {
backBtn.addEventListener('click', () => {
showMainPage();
});
}
// 绑定功能开关按钮事件
if (thinkingBtn) {
thinkingBtn.addEventListener('click', () => {
enableThinking = !enableThinking;
thinkingBtn.classList.toggle('active', enableThinking);
});
}
if (searchBtn) {
searchBtn.addEventListener('click', () => {
enableSearch = !enableSearch;
searchBtn.classList.toggle('active', enableSearch);
});
}
// 绑定输入事件
userInput.addEventListener('keydown', handleKeyDown);
userInput.addEventListener('input', (e) => autoResize(e.target));
sendBtn.addEventListener('click', sendMessage);
// 绑定置顶置底按钮事件
const scrollTopBtn = document.getElementById('scrollTopBtn');
const scrollBottomBtn = document.getElementById('scrollBottomBtn');
if (scrollTopBtn) {
scrollTopBtn.addEventListener('click', () => {
if (messagesContainer) {
messagesContainer.scrollTo({
top: 0,
behavior: 'smooth'
});
}
});
}
if (scrollBottomBtn) {
scrollBottomBtn.addEventListener('click', () => {
if (messagesContainer) {
messagesContainer.scrollTo({
top: messagesContainer.scrollHeight,
behavior: 'smooth'
});
}
});
}
// 绑定上传按钮事件
const attachBtn = document.getElementById('attachBtn');
const attachPanel = document.getElementById('attachPanel');
const imageInput = document.getElementById('imageInput');
const fileInput = document.getElementById('fileInput');
if (attachBtn) {
attachBtn.addEventListener('click', (e) => {
e.stopPropagation();
attachPanel.classList.toggle('show');
});
}
document.addEventListener('click', (e) => {
if (attachPanel && attachPanel.classList.contains('show') &&
!attachPanel.contains(e.target) && !attachBtn.contains(e.target)) {
attachPanel.classList.remove('show');
}
});
attachPanel.querySelectorAll('.attach-item').forEach(item => {
item.addEventListener('click', () => {
const type = item.getAttribute('data-type');
attachPanel.classList.remove('show');
if (type === 'image') {
imageInput.click();
} else if (type === 'file') {
fileInput.click();
}
});
});
imageInput.addEventListener('change', handleImageUpload);
fileInput.addEventListener('change', handleFileUpload);
// 渲染消息
renderMessages();
userInput.focus();
}
// 设置长按事件
// 全局变量:当前操作的对话ID
let currentActionConvId = null;
function setupLongPressEvents(container) {
let longPressTimer = null;
container.addEventListener('touchstart', (e) => {
const item = e.target.closest('.conversation-item');
if (item) {
longPressTimer = setTimeout(() => {
currentActionConvId = item.getAttribute('data-id');
showActionMenu(currentActionConvId);
}, 500);
}
});
container.addEventListener('touchend', () => {
if (longPressTimer) {
clearTimeout(longPressTimer);
longPressTimer = null;
}
});
container.addEventListener('touchmove', () => {
if (longPressTimer) {
clearTimeout(longPressTimer);
longPressTimer = null;
}
});
container.addEventListener('mousedown', (e) => {
const item = e.target.closest('.conversation-item');
if (item) {
longPressTimer = setTimeout(() => {
currentActionConvId = item.getAttribute('data-id');
showActionMenu(currentActionConvId);
}, 500);
}
});
container.addEventListener('mouseup', () => {
if (longPressTimer) {
clearTimeout(longPressTimer);
longPressTimer = null;
}
});
container.addEventListener('mouseleave', () => {
if (longPressTimer) {
clearTimeout(longPressTimer);
longPressTimer = null;
}
});
}
function showConversationList() {
currentConversation = null;
showMainPage(); // 返回主页,会正确显示过滤后的对话列表
}
// 排序对话(置顶在前)
function sortConversations() {
return [...conversations].sort((a, b) => {
// 置顶优先
if (a.is_pinned && !b.is_pinned) return -1;
if (!a.is_pinned && b.is_pinned) return 1;
// 然后按更新时间
return b.updatedAt - a.updatedAt;
});
}
// 排序普通对话(没有智能体的)
function sortNormalConversations(convList) {
return [...convList].sort((a, b) => {
// 置顶优先
if (a.is_pinned && !b.is_pinned) return -1;
if (!a.is_pinned && b.is_pinned) return 1;
// 然后按更新时间
return b.updatedAt - a.updatedAt;
});
}
// 搜索普通对话(不包含智能体对话)
function searchConversations(keyword) {
const searchResults = document.getElementById('searchResults');
if (!searchResults) return;
keyword = keyword.toLowerCase();
// 只搜索没有智能体的普通对话
const normalConversations = conversations.filter(conv => !conv.agentId);
// 搜索标题和消息内容
const results = normalConversations.filter(conv => {
// 搜索标题
if (conv.title.toLowerCase().includes(keyword)) return true;
// 搜索消息内容
if (conv.messages.some(m => m.content.toLowerCase().includes(keyword))) return true;
return false;
});
if (results.length === 0) {
searchResults.innerHTML = '未找到相关对话
';
return;
}
searchResults.innerHTML = results.map(conv => {
// 找到匹配的消息片段
let matchSnippet = '';
const matchedMsg = conv.messages.find(m => m.content.toLowerCase().includes(keyword));
if (matchedMsg) {
const content = matchedMsg.content;
const idx = content.toLowerCase().indexOf(keyword);
const start = Math.max(0, idx - 30);
const end = Math.min(content.length, idx + keyword.length + 30);
matchSnippet = (start > 0 ? '...' : '') + content.slice(start, end) + (end < content.length ? '...' : '');
}
return `
${conv.is_pinned ? '
📌' : ''}
${escapeHtml(conv.title)}
${matchSnippet ? `
${escapeHtml(matchSnippet)}
` : ''}
${conv.messages.length} 条消息 · ${formatTime(conv.updatedAt)}
`;
}).join('');
}
// 显示操作菜单
function showActionMenu(convId) {
const actionMenu = document.getElementById('actionMenu');
const conv = conversations.find(c => c.id === convId);
if (!actionMenu || !conv) return;
// 更新置顶按钮文字
const pinText = document.getElementById('pinText');
if (pinText) {
pinText.textContent = conv.is_pinned ? '取消置顶' : '置顶';
}
// 显示菜单
actionMenu.classList.add('show');
}
// 隐藏操作菜单
function hideActionMenu() {
const actionMenu = document.getElementById('actionMenu');
if (actionMenu) {
actionMenu.classList.remove('show');
}
}
// 处理操作菜单动作
function handleActionMenuAction(action, convId) {
const conv = conversations.find(c => c.id === convId);
if (!conv) return;
switch (action) {
case 'rename':
renameConversation(convId);
break;
case 'share':
shareConversation(convId);
break;
case 'pin':
togglePinConversation(convId);
break;
case 'delete':
deleteConversation(convId);
break;
}
}
// 重命名对话
function renameConversation(convId) {
const conv = conversations.find(c => c.id === convId);
if (!conv) return;
const newTitle = prompt('请输入新的对话标题:', conv.title);
if (newTitle && newTitle.trim() && newTitle !== conv.title) {
conv.title = newTitle.trim();
conv.updatedAt = Date.now();
saveConversations();
showConversationList();
showToast('已重命名');
}
}
// 分享对话
function shareConversation(convId) {
const conv = conversations.find(c => c.id === convId);
if (!conv) return;
// 构建分享内容
const shareContent = `【${conv.title}】\n\n${conv.messages.map(m =>
`${m.role === 'user' ? '👤 用户' : '🤖 AI'}: ${m.content}`
).join('\n\n')}`;
// 复制到剪贴板
try {
const textarea = document.createElement('textarea');
textarea.value = shareContent;
textarea.style.position = 'fixed';
textarea.style.top = '0';
textarea.style.left = '0';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
showToast('对话已复制到剪贴板');
} catch (err) {
showToast('分享失败');
}
}
// 置顶/取消置顶对话
function togglePinConversation(convId) {
const conv = conversations.find(c => c.id === convId);
if (!conv) return;
conv.is_pinned = !conv.is_pinned;
conv.updatedAt = Date.now();
saveConversations();
showConversationList();
showToast(conv.is_pinned ? '已置顶' : '已取消置顶');
}
// 创建新对话
function createNewConversation() {
// 检查未登录用户的对话限制
if (!currentUser && !canCreateNewChat()) {
showLimitDialog('chat_session');
return;
}
const newConv = {
id: Date.now().toString(),
title: '新对话',
messages: [],
createdAt: Date.now(),
updatedAt: Date.now()
};
conversations.unshift(newConv);
saveConversations();
// 增加对话会话计数(未登录用户)
incrementChatSession();
openConversation(newConv.id);
}
// 打开对话
function openConversation(id) {
currentConversation = conversations.find(c => c.id === id);
if (!currentConversation) {
showConversationList();
return;
}
// 渲染对话页面
const chatHtml = `
`;
appContainer.innerHTML = chatHtml;
// 重新获取 DOM 元素
messagesContainer = document.getElementById('messagesContainer');
messagesDiv = document.getElementById('messages');
userInput = document.getElementById('userInput');
sendBtn = document.getElementById('sendBtn');
welcome = document.getElementById('welcome');
thinkingBtn = document.getElementById('thinkingBtn');
searchBtn = document.getElementById('searchBtn');
// 重置自动滚动状态
autoScrollEnabled = true;
// 设置滚动监听
setupScrollListener();
// 绑定按钮事件
const backBtn = document.getElementById('backBtn');
if (backBtn) backBtn.addEventListener('click', showConversationList);
const clearBtn = document.getElementById('clearBtn');
if (clearBtn) clearBtn.addEventListener('click', clearCurrentChat);
// 绑定功能开关按钮事件
if (thinkingBtn) {
thinkingBtn.addEventListener('click', () => {
enableThinking = !enableThinking;
thinkingBtn.classList.toggle('active', enableThinking);
});
}
if (searchBtn) {
searchBtn.addEventListener('click', () => {
enableSearch = !enableSearch;
searchBtn.classList.toggle('active', enableSearch);
});
}
// 绑定置顶置底按钮事件
const scrollTopBtn = document.getElementById('scrollTopBtn');
const scrollBottomBtn = document.getElementById('scrollBottomBtn');
if (scrollTopBtn) {
scrollTopBtn.addEventListener('click', () => {
if (messagesContainer) {
messagesContainer.scrollTo({
top: 0,
behavior: 'smooth'
});
}
});
}
if (scrollBottomBtn) {
scrollBottomBtn.addEventListener('click', () => {
if (messagesContainer) {
messagesContainer.scrollTo({
top: messagesContainer.scrollHeight,
behavior: 'smooth'
});
}
});
}
// 绑定输入事件
userInput.addEventListener('keydown', handleKeyDown);
userInput.addEventListener('input', (e) => autoResize(e.target));
sendBtn.addEventListener('click', sendMessage);
// 绑定上传按钮事件
const attachBtn = document.getElementById('attachBtn');
const attachPanel = document.getElementById('attachPanel');
const imageInput = document.getElementById('imageInput');
const fileInput = document.getElementById('fileInput');
if (attachBtn) {
attachBtn.addEventListener('click', (e) => {
e.stopPropagation(); // 阻止冒泡到 document
attachPanel.classList.toggle('show');
});
}
// 点击其他地方关闭面板
document.addEventListener('click', (e) => {
if (attachPanel && attachPanel.classList.contains('show') &&
!attachPanel.contains(e.target) && !attachBtn.contains(e.target)) {
attachPanel.classList.remove('show');
}
});
// 上传选项点击
attachPanel.querySelectorAll('.attach-item').forEach(item => {
item.addEventListener('click', () => {
const type = item.getAttribute('data-type');
attachPanel.classList.remove('show');
if (type === 'image') {
imageInput.click();
} else if (type === 'file') {
fileInput.click();
}
});
});
// 图片上传处理
imageInput.addEventListener('change', handleImageUpload);
// 文件上传处理
fileInput.addEventListener('change', handleFileUpload);
// 绑定快捷按钮事件
document.querySelectorAll('.quick-btn').forEach(btn => {
btn.addEventListener('click', () => {
const text = btn.getAttribute('data-text');
userInput.value = text;
sendMessage();
});
});
// 渲染消息
renderMessages();
userInput.focus();
}
// 删除对话
function deleteConversation(id) {
if (!confirm('确定要删除这个对话吗?')) return;
conversations = conversations.filter(c => c.id !== id);
saveConversations();
showConversationList();
}
// ==================== 对话页面 ====================
// 自动调整输入框高度
function autoResize(textarea) {
textarea.style.height = 'auto';
textarea.style.height = Math.min(textarea.scrollHeight, 120) + 'px';
}
// 处理键盘事件
function handleKeyDown(event) {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault();
sendMessage();
}
}
// 发送快捷消息
function sendQuickMessage(text) {
userInput.value = text;
sendMessage();
}
// 发送消息(流式输出)
async function sendMessage() {
if (!currentConversation) return;
const text = userInput.value.trim();
if (!text || isLoading) return;
// 检查未登录用户的消息限制
if (!currentUser) {
if (currentConversation.agentId) {
// 智能体对话
if (!canSendAgentMessage()) {
showLimitDialog('agent_message');
return;
}
} else {
// 普通对话
if (!canSendChatMessage()) {
showLimitDialog('chat_message');
return;
}
}
}
// 隐藏欢迎界面
welcome.style.display = 'none';
// 添加用户消息
currentConversation.messages.push({ role: 'user', content: text });
// 更新对话标题(第一条用户消息)
if (currentConversation.title === '新对话') {
currentConversation.title = text.slice(0, 30) + (text.length > 30 ? '...' : '');
// 更新标题显示
const titleEl = document.querySelector('.header h1');
if (titleEl) {
titleEl.textContent = currentConversation.title;
}
}
currentConversation.updatedAt = Date.now();
saveConversations();
// 增加消息计数(未登录用户)
if (!currentUser) {
if (currentConversation.agentId) {
incrementAgentMessage();
} else {
incrementChatMessage();
}
}
// 发送新消息时启用自动滚动
autoScrollEnabled = true;
renderMessages();
userInput.value = '';
autoResize(userInput);
// 调用流式生成
await streamGenerate(currentConversation.messages.length - 1);
}
// 流式生成 AI 回复
async function streamGenerate(userMsgIndex) {
isLoading = true;
sendBtn.disabled = true;
const aiMessageIndex = currentConversation.messages.length;
const userMessage = currentConversation.messages[userMsgIndex];
// 如果开启联网搜索,先执行搜索
let searchResults = null;
if (enableSearch && userMessage.role === 'user') {
searchResults = await performSearch(userMessage.content);
}
// 只有开启深度思考时才添加 thinking 字段,开启搜索时添加 search_results 字段
currentConversation.messages.push({
role: 'assistant',
content: '',
...(enableThinking ? { thinking: '' } : {}),
...(searchResults ? { search_results: searchResults } : {})
});
renderMessages();
const lastMessageEl = messagesDiv.lastElementChild;
const contentEl = lastMessageEl.querySelector('.message-content');
const thinkingEl = lastMessageEl.querySelector('.thinking-content');
// 深度思考模式:思考块默认展开
if (enableThinking && thinkingEl) {
const thinkingBlock = lastMessageEl.querySelector('.thinking-block');
if (thinkingBlock) thinkingBlock.classList.add('expanded');
thinkingEl.innerHTML = '思考中...';
}
contentEl.innerHTML = '▌';
// 显示停止生成按钮
showStopGenerateBtn();
try {
// 构建消息数组
let messagesToSend = currentConversation.messages.slice(0, aiMessageIndex).map(m => ({
role: m.role,
content: m.content
}));
// 如果有搜索结果,将搜索内容添加到消息中
if (searchResults) {
const searchContext = formatSearchResultsForLLM(searchResults);
messagesToSend.push({
role: 'system',
content: `以下是搜索结果,请根据这些信息回答用户问题:\n\n${searchContext}`
});
}
// 构建请求体 - 统一使用 glm-4.5-air,通过 thinking 参数控制
const requestBody = {
model: CONFIG.model,
messages: messagesToSend,
max_tokens: CONFIG.maxTokens,
stream: true,
thinking: {
type: enableThinking ? 'enabled' : 'disabled'
}
};
const response = await fetch(CONFIG.apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${CONFIG.apiKey}`
},
body: JSON.stringify(requestBody)
});
if (!response.ok) {
throw new Error(`API 错误: ${response.status}`);
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
let thinkingOutputStarted = false; // 正式内容是否开始输出
let abortController = new AbortController(); // 用于中断流
// 绑定停止按钮事件
const stopBtn = document.getElementById('stopGenerateBtn');
if (stopBtn) {
stopBtn.onclick = () => {
abortController.abort();
isLoading = false;
sendBtn.disabled = false;
hideStopGenerateBtn();
// 更新最终内容
if (thinkingEl && enableThinking && currentConversation.messages[aiMessageIndex].thinking) {
thinkingEl.innerHTML = renderMarkdown(currentConversation.messages[aiMessageIndex].thinking);
}
contentEl.innerHTML = renderMarkdown(currentConversation.messages[aiMessageIndex].content);
currentConversation.updatedAt = Date.now();
saveConversations();
renderMessages();
};
}
while (true) {
if (abortController.signal.aborted) break; // 检查是否已停止
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (line.startsWith('data: ')) {
const jsonStr = line.slice(6).trim();
if (jsonStr === '[DONE]') continue;
try {
const data = JSON.parse(jsonStr);
const delta = data.choices?.[0]?.delta;
if (delta) {
// 只有开启深度思考时才处理思考内容
if (enableThinking && (delta.reasoning_content || delta.thinking)) {
const thinkingChunk = delta.reasoning_content || delta.thinking;
currentConversation.messages[aiMessageIndex].thinking += thinkingChunk;
if (thinkingEl) {
thinkingEl.innerHTML = renderMarkdown(currentConversation.messages[aiMessageIndex].thinking) + '▌';
}
// 确保思考块展开
const thinkingBlock = lastMessageEl.querySelector('.thinking-block');
if (thinkingBlock && !thinkingBlock.classList.contains('expanded')) {
thinkingBlock.classList.add('expanded');
}
scrollToBottom();
}
// 处理正式回复内容
if (delta.content) {
// 如果开启深度思考且开始输出正式内容,说明思考完成,立即折叠思考块
if (enableThinking && !thinkingOutputStarted && currentConversation.messages[aiMessageIndex].thinking) {
thinkingOutputStarted = true;
// 折叠思考内容
const thinkingBlock = lastMessageEl.querySelector('.thinking-block');
if (thinkingBlock) thinkingBlock.classList.remove('expanded');
if (thinkingEl) thinkingEl.innerHTML = renderMarkdown(currentConversation.messages[aiMessageIndex].thinking);
}
currentConversation.messages[aiMessageIndex].content += delta.content;
contentEl.innerHTML = renderMarkdown(currentConversation.messages[aiMessageIndex].content) + '▌';
scrollToBottom();
}
}
} catch (e) {}
}
}
}
// 最终渲染
if (thinkingEl && enableThinking && currentConversation.messages[aiMessageIndex].thinking) {
thinkingEl.innerHTML = renderMarkdown(currentConversation.messages[aiMessageIndex].thinking);
}
contentEl.innerHTML = renderMarkdown(currentConversation.messages[aiMessageIndex].content);
} catch (error) {
console.error('Error:', error);
currentConversation.messages[aiMessageIndex].content = `抱歉,出现了错误:${error.message}\n\n请检查网络连接后重试。`;
contentEl.innerHTML = renderMarkdown(currentConversation.messages[aiMessageIndex].content);
} finally {
isLoading = false;
sendBtn.disabled = false;
hideStopGenerateBtn();
currentConversation.updatedAt = Date.now();
saveConversations();
renderMessages();
// 自动总结标题:第一次对话和每隔5次对话
const totalMessages = currentConversation.messages.length;
// 第一次对话(用户+AI=2条)或每5次对话(10条)
if (totalMessages === 2 || totalMessages % 10 === 0) {
await generateConversationTitle();
}
}
}
// 执行 Tavily 搜索
async function performSearch(query) {
try {
const response = await fetch(CONFIG.tavilyApiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${CONFIG.tavilyApiKey}`
},
body: JSON.stringify({
query: query,
max_results: 10,
include_raw_content: false
})
});
if (!response.ok) {
console.error('搜索失败:', response.status);
return null;
}
const data = await response.json();
return data.results || [];
} catch (error) {
console.error('搜索错误:', error);
return null;
}
}
// 格式化搜索结果给 LLM
function formatSearchResultsForLLM(results) {
if (!results || results.length === 0) return '无搜索结果';
return results.map((r, i) =>
`${i + 1}. 【${r.title}】\n来源: ${r.url}\n摘要: ${r.content || '无摘要'}\n`
).join('\n');
}
// 显示停止生成按钮
function showStopGenerateBtn() {
// 检查是否已存在
if (document.getElementById('stopGenerateBtn')) return;
const stopBtn = document.createElement('button');
stopBtn.id = 'stopGenerateBtn';
stopBtn.className = 'stop-generate-btn';
stopBtn.innerHTML = `
停止生成
`;
// 插入到消息容器底部
if (messagesContainer) {
messagesContainer.appendChild(stopBtn);
}
}
// 隐藏停止生成按钮
function hideStopGenerateBtn() {
const stopBtn = document.getElementById('stopGenerateBtn');
if (stopBtn) {
stopBtn.remove();
}
}
// 生成对话标题
async function generateConversationTitle() {
if (!currentConversation) return;
console.log('开始生成标题,当前消息数:', currentConversation.messages.length);
// 构建对话摘要
const conversationText = currentConversation.messages.map(m =>
`${m.role === 'user' ? '用户' : 'AI'}: ${m.content.slice(0, 200)}`
).join('\n');
const titlePrompt = `请用不超过20个字总结以下对话的主题,只输出标题,不要其他内容:
${conversationText}`;
try {
const response = await fetch(CONFIG.apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${CONFIG.apiKey}`
},
body: JSON.stringify({
model: CONFIG.model,
messages: [{ role: 'user', content: titlePrompt }],
max_tokens: 100,
thinking: { type: 'disabled' } // 禁用思考模式,直接输出标题
})
});
console.log('标题API响应状态:', response.status);
if (response.ok) {
const data = await response.json();
console.log('标题API响应数据:', data);
const newTitle = data.choices?.[0]?.message?.content?.trim();
console.log('生成的标题:', newTitle);
if (newTitle && newTitle.length > 0) {
// 去掉可能的引号和多余符号
const cleanTitle = newTitle.replace(/^["'"']+|["'"']+$/g, '').trim();
if (cleanTitle.length > 0 && cleanTitle.length <= 30) {
currentConversation.title = cleanTitle;
currentConversation.updatedAt = Date.now();
saveConversations();
console.log('标题已保存:', cleanTitle);
// 更新页面标题显示
const titleEl = document.querySelector('.header h1');
if (titleEl) {
titleEl.textContent = cleanTitle;
}
// 更新侧边栏标题(如果在对话列表页面)
const convItem = document.querySelector(`.conversation-item[data-id="${currentConversation.id}"] .conv-title`);
if (convItem) {
convItem.textContent = cleanTitle;
}
showToast('标题已更新');
}
}
} else {
const errorText = await response.text();
console.error('标题API错误:', errorText);
}
} catch (error) {
console.error('生成标题失败:', error);
}
}
// 重新生成 AI 回复
async function regenerate(index) {
if (!currentConversation || isLoading || index < 1) return;
const userMsgIndex = index - 1;
if (currentConversation.messages[userMsgIndex].role !== 'user') return;
currentConversation.messages.splice(index, 1);
currentConversation.updatedAt = Date.now();
saveConversations();
await streamGenerate(userMsgIndex);
}
// 删除消息
function deleteMessage(index) {
if (!currentConversation || isLoading) return;
const msg = currentConversation.messages[index];
if (msg.role === 'assistant') {
if (index > 0 && currentConversation.messages[index - 1].role === 'user') {
currentConversation.messages.splice(index - 1, 2);
} else {
currentConversation.messages.splice(index, 1);
}
} else {
if (index < currentConversation.messages.length - 1 && currentConversation.messages[index + 1].role === 'assistant') {
currentConversation.messages.splice(index, 2);
} else {
currentConversation.messages.splice(index, 1);
}
}
currentConversation.updatedAt = Date.now();
saveConversations();
renderMessages();
if (currentConversation.messages.length === 0) {
welcome.style.display = 'block';
}
}
// 复制消息(复制原文)
function copyMessage(index) {
if (!currentConversation) return;
const msg = currentConversation.messages[index];
// 如果是图片消息,复制图片描述或提示
let content = msg.content;
if (msg.image && content === '[图片]') {
content = '[图片: ' + (msg.imageName || '未命名') + ']';
}
// HTTP 环境下 navigator.clipboard 不工作,优先使用 fallback
try {
const textarea = document.createElement('textarea');
textarea.value = content;
textarea.style.position = 'fixed';
textarea.style.top = '0';
textarea.style.left = '0';
textarea.style.opacity = '0';
textarea.style.pointerEvents = 'none';
document.body.appendChild(textarea);
textarea.focus();
textarea.select();
const success = document.execCommand('copy');
document.body.removeChild(textarea);
if (success) {
showToast('已复制到剪贴板');
} else {
showToast('复制失败,请手动复制');
}
} catch (err) {
console.error('复制失败:', err);
showToast('复制失败,请手动复制');
}
}
// 清空当前对话
function clearCurrentChat() {
if (!currentConversation) return;
if (confirm('确定要清空当前对话吗?')) {
currentConversation.messages = [];
currentConversation.updatedAt = Date.now();
saveConversations();
renderMessages();
welcome.style.display = 'block';
}
}
// 渲染消息
function renderMessages() {
if (!currentConversation) return;
// 根据消息数量显示/隐藏欢迎界面
if (welcome) {
welcome.style.display = currentConversation.messages.length > 0 ? 'none' : 'block';
}
messagesDiv.innerHTML = currentConversation.messages.map((msg, index) => {
const isUser = msg.role === 'user';
const avatar = isUser ? '👤' : '🤖';
// 处理消息内容(支持图片)
let contentHtml = '';
if (msg.image) {
// 图片消息
contentHtml = ``;
if (msg.content && msg.content !== '[图片]') {
contentHtml += `${renderMarkdown(msg.content)}
`;
}
} else {
contentHtml = renderMarkdown(msg.content);
}
// 思考内容块(仅AI消息)
let thinkingHtml = '';
if (!isUser && 'thinking' in msg) {
// 判断是否是当前正在生成的消息(有thinking字段且正在加载)
const isGenerating = index === currentConversation.messages.length - 1 && isLoading && enableThinking;
// 思考进行中且没有正式内容时展开
const expandedClass = isGenerating && !msg.content ? 'expanded' : '';
thinkingHtml = `
${renderMarkdown(msg.thinking || '思考中...')}
`;
}
// 搜索结果块(仅AI消息,放在思考块前面)
let searchHtml = '';
if (!isUser && msg.search_results && msg.search_results.length > 0) {
searchHtml = `
${msg.search_results.map((r, i) => `
`).join('')}
`;
}
const copyIcon = ``;
const actions = isUser
? ``
: ``;
return `
${avatar}
${searchHtml}
${thinkingHtml}
${contentHtml}
${actions}
`;
}).join('');
// 绑定消息操作按钮事件(事件委托)
messagesDiv.querySelectorAll('.copy-btn').forEach(btn => {
btn.addEventListener('click', () => copyMessage(parseInt(btn.dataset.index)));
});
messagesDiv.querySelectorAll('.regenerate-btn').forEach(btn => {
btn.addEventListener('click', () => regenerate(parseInt(btn.dataset.index)));
});
messagesDiv.querySelectorAll('.delete-btn').forEach(btn => {
btn.addEventListener('click', () => deleteMessage(parseInt(btn.dataset.index)));
});
scrollToBottom();
}
// 折叠/展开思考内容
function toggleThinking(block) {
block.classList.toggle('expanded');
}
// 折叠/展开搜索结果
function toggleSearchResults(block) {
block.classList.toggle('expanded');
}
// ==================== 工具函数 ====================
// 渲染 Markdown
function renderMarkdown(text) {
if (!text) return '';
marked.setOptions({
breaks: true,
gfm: true
});
return marked.parse(text);
}
// 滚动到底部(智能滚动:只在用户已在底部时自动滚动)
function scrollToBottom() {
if (messagesContainer) {
// 判断用户是否在底部附近(距离底部100px以内)
const isNearBottom = messagesContainer.scrollHeight - messagesContainer.scrollTop - messagesContainer.clientHeight < 100;
if (isNearBottom || autoScrollEnabled) {
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
}
}
// 监听用户滚动行为
function setupScrollListener() {
if (messagesContainer) {
messagesContainer.addEventListener('scroll', () => {
// 判断用户是否在底部附近
const isNearBottom = messagesContainer.scrollHeight - messagesContainer.scrollTop - messagesContainer.clientHeight < 100;
if (isNearBottom) {
autoScrollEnabled = true;
} else {
// 用户往上滚动,停止自动滚动
autoScrollEnabled = false;
}
});
}
}
// 保存对话列表
function saveConversations() {
localStorage.setItem('conversations', JSON.stringify(conversations));
}
// 显示提示
function showToast(message) {
const toast = document.createElement('div');
toast.className = 'toast';
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => toast.classList.add('show'), 10);
setTimeout(() => {
toast.classList.remove('show');
setTimeout(() => document.body.removeChild(toast), 300);
}, 2000);
}
// HTML转义
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// 格式化时间
function formatTime(timestamp) {
const date = new Date(timestamp);
const now = new Date();
const diff = now - date;
if (diff < 60000) return '刚刚';
if (diff < 3600000) return Math.floor(diff / 60000) + '分钟前';
if (diff < 86400000) return Math.floor(diff / 3600000) + '小时前';
if (diff < 604800000) return Math.floor(diff / 86400000) + '天前';
return date.toLocaleDateString('zh-CN');
}
// PWA 注册
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('sw.js').catch(() => {});
}
// ==================== 文件上传处理 ====================
// 处理图片上传
async function handleImageUpload(e) {
const file = e.target.files[0];
if (!file) return;
// 读取图片为base64
const reader = new FileReader();
reader.onload = async (event) => {
const base64 = event.target.result;
// 添加用户消息(显示图片)
currentConversation.messages.push({
role: 'user',
content: '[图片]',
image: base64,
imageName: file.name
});
currentConversation.updatedAt = Date.now();
saveConversations();
renderMessages();
// 隐藏欢迎界面
if (welcome) welcome.style.display = 'none';
// 调用AI生成
await streamGenerateWithImage(base64, file.name);
};
reader.readAsDataURL(file);
// 清空input以便再次选择同一文件
e.target.value = '';
}
// 处理文件上传
async function handleFileUpload(e) {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = async (event) => {
const content = event.target.result;
const fileName = file.name;
// 添加用户消息
currentConversation.messages.push({
role: 'user',
content: `[文件: ${fileName}]\\n\\n${content.slice(0, 500)}${content.length > 500 ? '...' : ''}`
});
currentConversation.updatedAt = Date.now();
saveConversations();
renderMessages();
if (welcome) welcome.style.display = 'none';
// 调用AI生成
await streamGenerateWithFile(content, fileName);
};
// 根据文件类型读取
if (file.name.endsWith('.pdf') || file.name.endsWith('.doc') || file.name.endsWith('.docx')) {
// PDF/Word文件暂时只显示文件名
showToast('PDF/Word文件暂不支持解析,请上传文本文件');
e.target.value = '';
return;
}
reader.readAsText(file);
e.target.value = '';
}
// 带图片的流式生成
async function streamGenerateWithImage(base64, imageName) {
isLoading = true;
sendBtn.disabled = true;
const aiMessageIndex = currentConversation.messages.length;
currentConversation.messages.push({ role: 'assistant', content: '' });
renderMessages();
const lastMessageEl = messagesDiv.lastElementChild;
const contentEl = lastMessageEl.querySelector('.message-content');
contentEl.innerHTML = '▌';
try {
// 构建多模态消息
const messages = currentConversation.messages.slice(0, aiMessageIndex).map(m => {
if (m.image) {
return {
role: m.role,
content: [
{ type: 'image_url', image_url: { url: m.image } },
{ type: 'text', text: '请分析这张图片' }
]
};
}
return { role: m.role, content: m.content };
});
const response = await fetch(CONFIG.apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${CONFIG.apiKey}`
},
body: JSON.stringify({
model: 'glm-4v-flash', // 视觉模型
messages: messages,
max_tokens: CONFIG.maxTokens,
stream: true
})
});
if (!response.ok) {
throw new Error(`API错误: ${response.status}`);
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (line.startsWith('data: ')) {
const jsonStr = line.slice(6).trim();
if (jsonStr === '[DONE]') continue;
try {
const data = JSON.parse(jsonStr);
if (data.choices && data.choices[0]?.delta?.content) {
currentConversation.messages[aiMessageIndex].content += data.choices[0].delta.content;
contentEl.innerHTML = renderMarkdown(currentConversation.messages[aiMessageIndex].content) + '▌';
scrollToBottom();
}
} catch (err) {}
}
}
}
contentEl.innerHTML = renderMarkdown(currentConversation.messages[aiMessageIndex].content);
} catch (error) {
console.error('Error:', error);
currentConversation.messages[aiMessageIndex].content = `抱歉,图片分析失败:${error.message}`;
contentEl.innerHTML = renderMarkdown(currentConversation.messages[aiMessageIndex].content);
} finally {
isLoading = false;
sendBtn.disabled = false;
currentConversation.updatedAt = Date.now();
saveConversations();
renderMessages();
}
}
// 带文件的流式生成
async function streamGenerateWithFile(content, fileName) {
isLoading = true;
sendBtn.disabled = true;
const aiMessageIndex = currentConversation.messages.length;
currentConversation.messages.push({ role: 'assistant', content: '' });
renderMessages();
const lastMessageEl = messagesDiv.lastElementChild;
const contentEl = lastMessageEl.querySelector('.message-content');
contentEl.innerHTML = '▌';
try {
const messages = currentConversation.messages.slice(0, aiMessageIndex).map(m => ({
role: m.role,
content: m.content
}));
// 添加文件内容作为系统提示
messages.unshift({
role: 'system',
content: `以下是用户上传的文件内容,请根据内容回答问题:\\n文件名:${fileName}\\n内容:\\n${content}`
});
const response = await fetch(CONFIG.apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${CONFIG.apiKey}`
},
body: JSON.stringify({
model: CONFIG.model,
messages: messages,
max_tokens: CONFIG.maxTokens,
stream: true
})
});
if (!response.ok) {
throw new Error(`API错误: ${response.status}`);
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (line.startsWith('data: ')) {
const jsonStr = line.slice(6).trim();
if (jsonStr === '[DONE]') continue;
try {
const data = JSON.parse(jsonStr);
if (data.choices && data.choices[0]?.delta?.content) {
currentConversation.messages[aiMessageIndex].content += data.choices[0].delta.content;
contentEl.innerHTML = renderMarkdown(currentConversation.messages[aiMessageIndex].content) + '▌';
scrollToBottom();
}
} catch (err) {}
}
}
}
contentEl.innerHTML = renderMarkdown(currentConversation.messages[aiMessageIndex].content);
} catch (error) {
console.error('Error:', error);
currentConversation.messages[aiMessageIndex].content = `抱歉,文件处理失败:${error.message}`;
contentEl.innerHTML = renderMarkdown(currentConversation.messages[aiMessageIndex].content);
} finally {
isLoading = false;
sendBtn.disabled = false;
currentConversation.updatedAt = Date.now();
saveConversations();
renderMessages();
}
}