Compare commits

...

4 Commits

3 changed files with 926 additions and 11 deletions

View File

@@ -16,9 +16,39 @@ let conversations = []; // 对话列表
let currentConversation = null; // 当前对话
let isLoading = false;
// 当前页面状态
let currentPage = 'chats'; // chats | agents | profile
// 智能体数据
let agents = [
// 热门智能体
{ id: 'assistant', name: '通用助手', avatar: '🤖', category: 'hot', desc: '能回答各类问题,帮助写作、分析、解答疑惑', systemPrompt: '你是一个智能助手,能够回答各类问题,帮助用户解决问题。' },
{ id: 'writer', name: '写作助手', avatar: '✍️', category: 'hot', desc: '专注于文章写作、文案创作、内容润色', systemPrompt: '你是一个专业的写作助手,擅长各类文章写作、文案创作和内容润色。' },
{ id: 'coder', name: '编程助手', avatar: '👨‍💻', category: 'hot', desc: '精通编程语言,解答技术问题,生成代码', systemPrompt: '你是一个专业的编程助手,精通各类编程语言,能够解答技术问题并生成高质量代码。' },
{ id: 'translator', name: '翻译助手', avatar: '🌐', category: 'hot', desc: '多语言翻译,精准表达,文化适配', systemPrompt: '你是一个专业的翻译助手,精通多语言翻译,能够精准表达并适配文化差异。' },
// 工作助手
{ id: 'assistant-work', name: '工作助手', avatar: '💼', category: 'work', desc: '职场问题解答,工作效率提升', systemPrompt: '你是一个工作助手,帮助解决职场问题,提升工作效率。' },
{ id: 'ppt', name: 'PPT助手', avatar: '📊', category: 'work', desc: 'PPT内容生成结构优化设计建议', systemPrompt: '你是一个PPT助手擅长PPT内容生成、结构优化和设计建议。' },
{ id: 'excel', name: 'Excel助手', avatar: '📈', category: 'work', desc: 'Excel公式、数据分析、表格优化', systemPrompt: '你是一个Excel助手精通Excel公式、数据分析和表格优化。' },
// 学习助手
{ id: 'teacher', name: '学习助手', avatar: '📚', category: 'study', desc: '知识讲解,学习方法,考试辅导', systemPrompt: '你是一个学习助手,擅长知识讲解、学习方法指导和考试辅导。' },
{ id: 'english', name: '英语助手', avatar: '🔤', category: 'study', desc: '英语学习,语法纠正,口语练习', systemPrompt: '你是一个英语助手,帮助英语学习、语法纠正和口语练习。' },
{ id: 'math', name: '数学助手', avatar: '🔢', category: 'study', desc: '数学解题,公式推导,概念讲解', systemPrompt: '你是一个数学助手,擅长数学解题、公式推导和概念讲解。' },
// 生活助手
{ id: 'health', name: '健康助手', avatar: '🏥', category: 'life', desc: '健康咨询,养生建议,运动指导', systemPrompt: '你是一个健康助手,提供健康咨询、养生建议和运动指导。' },
{ id: 'travel', name: '旅行助手', avatar: '✈️', category: 'life', desc: '旅行规划,景点推荐,美食指南', systemPrompt: '你是一个旅行助手,擅长旅行规划、景点推荐和美食指南。' },
{ id: 'food', name: '美食助手', avatar: '🍳', category: 'life', desc: '菜谱推荐,烹饪技巧,营养搭配', systemPrompt: '你是一个美食助手,提供菜谱推荐、烹饪技巧和营养搭配建议。' },
];
let currentAgent = null; // 当前选中的智能体
// 功能开关
let enableThinking = false; // 深度思考
let enableSearch = false; // 联网搜索
let autoScrollEnabled = true; // 自动滚动(用户滚动后可关闭)
// DOM 元素(初始为 null在 openConversation 时重新获取)
let appContainer = null;
@@ -60,11 +90,567 @@ document.addEventListener('DOMContentLoaded', () => {
}
}
// 显示对话列表页面
showConversationList();
// 加载当前页面状态
const savedPage = localStorage.getItem('currentPage');
if (savedPage) {
currentPage = savedPage;
}
// 显示主页
showMainPage();
});
// ==================== 对话列表页面 ====================
// ==================== 主页(底部导航栏) ====================
function showMainPage() {
currentConversation = null;
currentAgent = null;
const mainHtml = `
<div class="main-page">
<div class="main-content" id="mainContent">
${renderCurrentPage()}
</div>
<!-- 底部导航栏 -->
<nav class="bottom-nav">
<div class="nav-item ${currentPage === 'chats' ? 'active' : ''}" data-page="chats">
<svg viewBox="0 0 24 24" width="24" height="24"><path fill="currentColor" d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/></svg>
<span>对话</span>
</div>
<div class="nav-item ${currentPage === 'agents' ? 'active' : ''}" data-page="agents">
<svg viewBox="0 0 24 24" width="24" height="24"><path fill="currentColor" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z"/></svg>
<span>智能体</span>
</div>
<div class="nav-item ${currentPage === 'profile' ? 'active' : ''}" data-page="profile">
<svg viewBox="0 0 24 24" width="24" height="24"><path fill="currentColor" d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg>
<span>我的</span>
</div>
</nav>
</div>
`;
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() {
return `
<div class="chats-page">
<header class="page-header">
<h1>对话</h1>
<button class="header-btn new-chat-btn" id="newChatBtn" title="新建对话">
<svg viewBox="0 0 24 24" width="20" height="20"><path fill="currentColor" d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
</button>
</header>
<div class="chats-content">
<div class="conversation-list" id="conversationList">
${conversations.length === 0
? '<div class="empty-list">暂无对话记录<br><br>点击右上角 + 开始新对话</div>'
: sortConversations().map(conv => `
<div class="conversation-item ${conv.is_pinned ? 'pinned' : ''}" data-id="${conv.id}">
${conv.is_pinned ? '<span class="pin-icon">📌</span>' : ''}
<div class="conv-title">${escapeHtml(conv.title)}</div>
<div class="conv-meta">${conv.messages.length} 条消息 · ${formatTime(conv.updatedAt)}</div>
</div>
`).join('')
}
</div>
</div>
</div>
`;
}
function bindChatsPageEvents() {
const newChatBtn = document.getElementById('newChatBtn');
if (newChatBtn) {
newChatBtn.addEventListener('click', createNewConversation);
}
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);
}
}
// ==================== 智能体页面 ====================
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');
return `
<div class="agents-page">
<header class="page-header">
<h1>智能体</h1>
</header>
<div class="agents-content">
<!-- 热门智能体 -->
<div class="agents-section">
<div class="section-title">🔥 热门智能体</div>
<div class="agents-grid">
${hotAgents.map(agent => `
<div class="agent-card" data-id="${agent.id}">
<div class="agent-avatar">${agent.avatar}</div>
<div class="agent-name">${agent.name}</div>
<div class="agent-desc">${agent.desc}</div>
</div>
`).join('')}
</div>
</div>
<!-- 工作助手 -->
<div class="agents-section">
<div class="section-title">💼 工作助手</div>
<div class="agents-grid">
${workAgents.map(agent => `
<div class="agent-card" data-id="${agent.id}">
<div class="agent-avatar">${agent.avatar}</div>
<div class="agent-name">${agent.name}</div>
<div class="agent-desc">${agent.desc}</div>
</div>
`).join('')}
</div>
</div>
<!-- 学习助手 -->
<div class="agents-section">
<div class="section-title">📚 学习助手</div>
<div class="agents-grid">
${studyAgents.map(agent => `
<div class="agent-card" data-id="${agent.id}">
<div class="agent-avatar">${agent.avatar}</div>
<div class="agent-name">${agent.name}</div>
<div class="agent-desc">${agent.desc}</div>
</div>
`).join('')}
</div>
</div>
<!-- 生活助手 -->
<div class="agents-section">
<div class="section-title">🏠 生活助手</div>
<div class="agents-grid">
${lifeAgents.map(agent => `
<div class="agent-card" data-id="${agent.id}">
<div class="agent-avatar">${agent.avatar}</div>
<div class="agent-name">${agent.name}</div>
<div class="agent-desc">${agent.desc}</div>
</div>
`).join('')}
</div>
</div>
</div>
</div>
`;
}
function bindAgentsPageEvents() {
document.querySelectorAll('.agent-card').forEach(card => {
card.addEventListener('click', () => {
const id = card.getAttribute('data-id');
openAgent(id);
});
});
}
// ==================== 我的页面 ====================
function renderProfilePage() {
return `
<div class="profile-page">
<header class="page-header">
<h1>我的</h1>
</header>
<div class="profile-content">
<div class="profile-card">
<div class="profile-avatar">👤</div>
<div class="profile-name">用户</div>
</div>
<div class="profile-section">
<div class="profile-item" id="settingsBtn">
<svg viewBox="0 0 24 24" width="20" height="20"><path fill="currentColor" d="M19.14 12.94c.04-.31.06-.63.06-.94 0-.31-.02-.63-.06-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.44.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.04.31-.06.63-.06.94s.02.63.06.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"/></svg>
<span>设置</span>
</div>
<div class="profile-item" id="aboutBtn">
<svg viewBox="0 0 24 24" width="20" height="20"><path fill="currentColor" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg>
<span>关于</span>
</div>
<div class="profile-item" id="clearDataBtn">
<svg viewBox="0 0 24 24" width="20" height="20"><path fill="currentColor" d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/></svg>
<span>清除数据</span>
</div>
</div>
<div class="profile-footer">
<p>AI助手 v2.8.0</p>
<p>基于智谱 GLM-4.5-Air</p>
</div>
</div>
</div>
`;
}
function bindProfilePageEvents() {
const clearDataBtn = document.getElementById('clearDataBtn');
if (clearDataBtn) {
clearDataBtn.addEventListener('click', () => {
if (confirm('确定要清除所有数据吗?这将删除所有对话记录。')) {
localStorage.clear();
conversations = [];
showToast('数据已清除');
showMainPage();
}
});
}
}
// 打开智能体对话
function openAgent(agentId) {
currentAgent = agents.find(a => a.id === agentId);
if (!currentAgent) return;
// 创建新对话并设置智能体
createNewConversation();
currentConversation.agentId = agentId;
currentConversation.title = currentAgent.name;
saveConversations();
// 显示对话界面
showAgentChatPage();
}
// 显示智能体对话界面
function showAgentChatPage() {
if (!currentAgent || !currentConversation) return;
const chatHtml = `
<div class="chat-page">
<header class="chat-header">
<button class="back-btn" id="backBtn">
<svg viewBox="0 0 24 24" width="24" height="24"><path fill="currentColor" d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg>
</button>
<div class="header-info">
<div class="agent-avatar-header">${currentAgent.avatar}</div>
<div class="header-text">
<h1>${currentAgent.name}</h1>
<p class="agent-desc-header">${currentAgent.desc}</p>
</div>
</div>
</header>
<div class="messages-container" id="messagesContainer">
<div class="welcome" id="welcome">
<div class="welcome-icon">${currentAgent.avatar}</div>
<h2>${currentAgent.name}</h2>
<p>${currentAgent.desc}</p>
<p class="welcome-tip">有什么可以帮你的吗?</p>
</div>
<div class="messages" id="messages"></div>
</div>
<!-- 功能开关栏 -->
<div class="feature-bar" id="featureBar">
<div class="feature-left">
<button class="feature-btn thinking-btn" id="thinkingBtn">
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>
<span>深度思考</span>
</button>
<button class="feature-btn search-btn" id="searchBtn">
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 7 9.5 7 14 9.01 14 9.5 11.99 14 9.5 14z"/></svg>
<span>联网搜索</span>
</button>
</div>
<div class="feature-right">
<button class="feature-btn nav-btn" id="scrollTopBtn">
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z"/></svg>
</button>
<button class="feature-btn nav-btn" id="scrollBottomBtn">
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6z"/></svg>
</button>
</div>
</div>
<div class="input-area">
<button class="attach-btn" id="attachBtn" title="上传文件">
<svg viewBox="0 0 24 24" width="24" height="24"><path fill="currentColor" d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
</button>
<textarea id="userInput" placeholder="输入消息..." rows="1"></textarea>
<button class="send-btn" id="sendBtn">
<svg viewBox="0 0 24 24" width="24" height="24"><path fill="currentColor" d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg>
</button>
</div>
<!-- 上传选项弹窗 -->
<div class="attach-panel" id="attachPanel">
<div class="attach-panel-content">
<div class="attach-item" data-type="image">
<div class="attach-icon">📷</div>
<div class="attach-label">上传图片</div>
</div>
<div class="attach-item" data-type="file">
<div class="attach-icon">📄</div>
<div class="attach-label">上传文件</div>
</div>
</div>
</div>
<input type="file" id="imageInput" accept="image/*" style="display:none">
<input type="file" id="fileInput" accept=".txt,.md,.pdf,.doc,.docx,.json,.csv" style="display:none">
<!-- 搜索栏 -->
<div class="search-bar" id="searchBar">
<div class="search-input-wrapper">
<svg viewBox="0 0 24 24" width="20" height="20"><path fill="currentColor" d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 7 9.5 7 14 9.01 14 9.5 11.99 14 9.5 14z"/></svg>
<input type="text" id="searchInput" placeholder="搜索对话标题或内容...">
<button class="search-close-btn" id="searchCloseBtn">
<svg viewBox="0 0 24 24" width="18" height="18"><path fill="currentColor" d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
</button>
</div>
<div class="search-results" id="searchResults"></div>
</div>
</div>
`;
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();
}
// 设置长按事件
function setupLongPressEvents(container) {
let longPressTimer = null;
let currentActionConvId = 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;
@@ -575,6 +1161,12 @@ function openConversation(id) {
thinkingBtn = document.getElementById('thinkingBtn');
searchBtn = document.getElementById('searchBtn');
// 重置自动滚动状态
autoScrollEnabled = true;
// 设置滚动监听
setupScrollListener();
// 绑定按钮事件
const backBtn = document.getElementById('backBtn');
if (backBtn) backBtn.addEventListener('click', showConversationList);
@@ -739,6 +1331,9 @@ async function sendMessage() {
currentConversation.updatedAt = Date.now();
saveConversations();
// 发送新消息时启用自动滚动
autoScrollEnabled = true;
renderMessages();
userInput.value = '';
autoResize(userInput);
@@ -1010,7 +1605,7 @@ async function generateConversationTitle() {
`${m.role === 'user' ? '用户' : 'AI'}: ${m.content.slice(0, 200)}`
).join('\n');
const titlePrompt = `请用不超过10个字总结以下对话的主题只输出标题不要其他内容
const titlePrompt = `请用不超过20个字总结以下对话的主题只输出标题不要其他内容
${conversationText}`;
try {
@@ -1023,7 +1618,8 @@ ${conversationText}`;
body: JSON.stringify({
model: CONFIG.model,
messages: [{ role: 'user', content: titlePrompt }],
max_tokens: 50
max_tokens: 100,
thinking: { type: 'disabled' } // 禁用思考模式,直接输出标题
})
});
@@ -1038,7 +1634,7 @@ ${conversationText}`;
if (newTitle && newTitle.length > 0) {
// 去掉可能的引号和多余符号
const cleanTitle = newTitle.replace(/^["'"']+|["'"']+$/g, '').trim();
if (cleanTitle.length > 0 && cleanTitle.length <= 20) {
if (cleanTitle.length > 0 && cleanTitle.length <= 30) {
currentConversation.title = cleanTitle;
currentConversation.updatedAt = Date.now();
saveConversations();
@@ -1297,10 +1893,32 @@ function renderMarkdown(text) {
return marked.parse(text);
}
// 滚动到底部
// 滚动到底部(智能滚动:只在用户已在底部时自动滚动)
function scrollToBottom() {
if (messagesContainer) {
messagesContainer.scrollTop = messagesContainer.scrollHeight;
// 判断用户是否在底部附近距离底部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;
}
});
}
}

View File

@@ -8,12 +8,12 @@
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<title>AI助手</title>
<link rel="stylesheet" href="style.css?v=2.7.5">
<link rel="stylesheet" href="style.css?v=3.0.0">
<link rel="manifest" href="manifest.json">
</head>
<body>
<div id="app"></div>
<script src="marked.min.js?v=2.7.5"></script>
<script src="app.js?v=2.7.5"></script>
<script src="marked.min.js?v=3.0.0"></script>
<script src="app.js?v=3.0.0"></script>
</body>
</html>

View File

@@ -29,6 +29,303 @@ body {
min-height: 100dvh;
}
/* ==================== 主页布局 ==================== */
.main-page {
display: flex;
flex-direction: column;
height: 100vh;
height: 100dvh;
background: #f5f5f5;
}
.main-content {
flex: 1;
overflow-y: auto;
}
/* 底部导航栏 */
.bottom-nav {
display: flex;
justify-content: space-around;
align-items: center;
padding: 8px 0;
background: white;
border-top: 1px solid var(--border-color);
position: sticky;
bottom: 0;
}
.nav-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
padding: 8px 16px;
color: var(--text-light);
cursor: pointer;
transition: all 0.2s;
}
.nav-item svg {
flex-shrink: 0;
}
.nav-item span {
font-size: 12px;
}
.nav-item:hover {
color: var(--primary);
}
.nav-item.active {
color: var(--primary);
}
/* ==================== 页面通用头部 ==================== */
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px;
background: white;
border-bottom: 1px solid var(--border-color);
}
.page-header h1 {
font-size: 20px;
font-weight: 600;
color: var(--text-color);
}
/* ==================== 对话页面 ==================== */
.chats-page {
display: flex;
flex-direction: column;
height: 100%;
}
.chats-content {
flex: 1;
padding: 16px;
overflow-y: auto;
}
.new-chat-btn {
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
background: var(--primary);
border: none;
border-radius: 10px;
color: white;
cursor: pointer;
transition: all 0.2s;
}
.new-chat-btn:hover {
background: #5a67d8;
transform: scale(1.05);
}
/* ==================== 智能体页面 ==================== */
.agents-page {
display: flex;
flex-direction: column;
height: 100%;
}
.agents-content {
flex: 1;
padding: 16px;
overflow-y: auto;
}
.agents-section {
margin-bottom: 24px;
}
.section-title {
font-size: 16px;
font-weight: 600;
color: var(--text-color);
margin-bottom: 12px;
}
.agents-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
}
.agent-card {
display: flex;
flex-direction: column;
align-items: center;
padding: 16px;
background: white;
border: 1px solid var(--border-color);
border-radius: 12px;
cursor: pointer;
transition: all 0.2s;
}
.agent-card:hover {
border-color: var(--primary);
background: rgba(102, 126, 234, 0.05);
transform: translateY(-2px);
}
.agent-avatar {
font-size: 32px;
margin-bottom: 8px;
}
.agent-name {
font-size: 14px;
font-weight: 600;
color: var(--text-color);
margin-bottom: 4px;
}
.agent-desc {
font-size: 12px;
color: var(--text-light);
text-align: center;
}
/* ==================== 我的页面 ==================== */
.profile-page {
display: flex;
flex-direction: column;
height: 100%;
}
.profile-content {
flex: 1;
padding: 16px;
}
.profile-card {
display: flex;
flex-direction: column;
align-items: center;
padding: 32px;
background: white;
border-radius: 12px;
margin-bottom: 16px;
}
.profile-avatar {
font-size: 48px;
margin-bottom: 12px;
}
.profile-name {
font-size: 18px;
font-weight: 600;
color: var(--text-color);
}
.profile-section {
background: white;
border-radius: 12px;
overflow: hidden;
}
.profile-item {
display: flex;
align-items: center;
gap: 12px;
padding: 16px;
border-bottom: 1px solid var(--border-color);
cursor: pointer;
transition: all 0.2s;
}
.profile-item:last-child {
border-bottom: none;
}
.profile-item:hover {
background: rgba(102, 126, 234, 0.05);
}
.profile-item svg {
color: var(--primary);
}
.profile-item span {
font-size: 16px;
color: var(--text-color);
}
.profile-footer {
margin-top: 32px;
text-align: center;
color: var(--text-light);
font-size: 14px;
}
.profile-footer p {
margin: 4px 0;
}
/* ==================== 智能体对话界面 ==================== */
.chat-page {
display: flex;
flex-direction: column;
height: 100vh;
height: 100dvh;
}
.chat-header {
display: flex;
align-items: center;
padding: 12px 16px;
background: linear-gradient(135deg, var(--primary) 0%, #764ba2 100%);
color: white;
}
.back-btn {
background: transparent;
border: none;
color: white;
padding: 8px;
cursor: pointer;
}
.header-info {
display: flex;
align-items: center;
gap: 12px;
flex: 1;
}
.agent-avatar-header {
font-size: 28px;
}
.header-text h1 {
font-size: 16px;
font-weight: 600;
margin: 0;
}
.agent-desc-header {
font-size: 12px;
opacity: 0.8;
margin: 0;
}
/* ==================== 对话列表页面 ==================== */
.conversation-list-page {