feat: 登录注册系统 + 游客使用限制
功能: - 登录/注册界面(本地存储用户数据) - 我的页面显示用户状态、登录/退出按钮 - 游客配额显示(对话会话/消息/智能体消息) 游客限制: - 每天最多1个对话会话 - 每天最多20条对话消息 - 只能使用通用助手智能体 - 每天最多20条智能体消息 - 登录用户无限制 限制提示: - 达到限制弹出提示对话框 - 提示登录解锁全部功能
This commit is contained in:
606
www/app.js
606
www/app.js
@@ -67,6 +67,166 @@ let pinnedAgents = {
|
||||
|
||||
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
|
||||
@@ -145,6 +305,21 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
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();
|
||||
|
||||
@@ -1113,6 +1288,8 @@ function showAgentFavoritePage() {
|
||||
// ==================== 我的页面 ====================
|
||||
|
||||
function renderProfilePage() {
|
||||
const quota = getRemainingQuota();
|
||||
|
||||
return `
|
||||
<div class="profile-page">
|
||||
<header class="page-header">
|
||||
@@ -1121,15 +1298,46 @@ function renderProfilePage() {
|
||||
|
||||
<div class="profile-content">
|
||||
<div class="profile-card">
|
||||
<div class="profile-avatar">👤</div>
|
||||
<div class="profile-name">用户</div>
|
||||
<div class="profile-avatar">${currentUser ? '👤' : '👤'}</div>
|
||||
<div class="profile-name">${currentUser ? currentUser.username : '游客'}</div>
|
||||
${currentUser
|
||||
? `<div class="profile-status">已登录</div>
|
||||
<button class="logout-btn" id="logoutBtn">退出登录</button>`
|
||||
: `<div class="profile-status guest">未登录</div>
|
||||
<div class="profile-login-buttons">
|
||||
<button class="login-btn" id="loginBtn">登录</button>
|
||||
<button class="register-btn" id="registerBtn">注册</button>
|
||||
<button class="skip-btn" id="skipBtn">跳过,继续使用</button>
|
||||
</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>
|
||||
${!currentUser && quota ? `
|
||||
<div class="quota-card">
|
||||
<div class="quota-title">今日使用配额(游客)</div>
|
||||
<div class="quota-items">
|
||||
<div class="quota-item">
|
||||
<span class="quota-label">对话会话</span>
|
||||
<span class="quota-value">${quota.chatSessionRemain}/${GUEST_LIMITS.maxChatSessionsPerDay}</span>
|
||||
</div>
|
||||
<div class="quota-item">
|
||||
<span class="quota-label">对话消息</span>
|
||||
<span class="quota-value">${quota.chatMsgRemain}/${GUEST_LIMITS.maxChatMessagesPerDay}</span>
|
||||
</div>
|
||||
<div class="quota-item">
|
||||
<span class="quota-label">智能体消息</span>
|
||||
<span class="quota-value">${quota.agentMsgRemain}/${GUEST_LIMITS.maxAgentMessagesPerDay}</span>
|
||||
</div>
|
||||
<div class="quota-item">
|
||||
<span class="quota-label">可用智能体</span>
|
||||
<span class="quota-value">仅通用助手 🤖</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="quota-tip">登录后解锁全部功能</div>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<div class="profile-section">
|
||||
<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>
|
||||
@@ -1141,7 +1349,7 @@ function renderProfilePage() {
|
||||
</div>
|
||||
|
||||
<div class="profile-footer">
|
||||
<p>AI助手 v3.2.1</p>
|
||||
<p>AI助手 v3.3.0</p>
|
||||
<p>基于智谱 GLM-4.5-Air</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1150,12 +1358,53 @@ function renderProfilePage() {
|
||||
}
|
||||
|
||||
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('确定要清除所有数据吗?这将删除所有对话记录。')) {
|
||||
if (confirm('确定要清除所有数据吗?这将删除所有对话记录和账户信息。')) {
|
||||
localStorage.clear();
|
||||
conversations = [];
|
||||
currentUser = null;
|
||||
dailyUsage = { date: null, chatSessions: 0, chatMessages: 0, agentUsed: null, agentMessages: 0 };
|
||||
showToast('数据已清除');
|
||||
showMainPage();
|
||||
}
|
||||
@@ -1163,6 +1412,280 @@ function bindProfilePageEvents() {
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 登录页面 ====================
|
||||
|
||||
function showLoginPage() {
|
||||
const loginHtml = `
|
||||
<div class="auth-page">
|
||||
<header class="auth-header">
|
||||
<button class="back-btn-white" id="loginBackBtn">
|
||||
<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>
|
||||
<h1>登录</h1>
|
||||
</header>
|
||||
|
||||
<div class="auth-content">
|
||||
<div class="auth-form">
|
||||
<div class="auth-input-group">
|
||||
<label>用户名</label>
|
||||
<input type="text" id="loginUsername" placeholder="请输入用户名" autocomplete="username">
|
||||
</div>
|
||||
<div class="auth-input-group">
|
||||
<label>密码</label>
|
||||
<input type="password" id="loginPassword" placeholder="请输入密码" autocomplete="current-password">
|
||||
</div>
|
||||
<button class="auth-submit-btn" id="loginSubmitBtn">登录</button>
|
||||
</div>
|
||||
|
||||
<div class="auth-footer">
|
||||
<p>还没有账号?<span class="auth-link" id="goToRegister">立即注册</span></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
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 = `
|
||||
<div class="auth-page">
|
||||
<header class="auth-header">
|
||||
<button class="back-btn-white" id="registerBackBtn">
|
||||
<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>
|
||||
<h1>注册</h1>
|
||||
</header>
|
||||
|
||||
<div class="auth-content">
|
||||
<div class="auth-form">
|
||||
<div class="auth-input-group">
|
||||
<label>用户名</label>
|
||||
<input type="text" id="registerUsername" placeholder="请输入用户名(3-20字符)" autocomplete="username">
|
||||
</div>
|
||||
<div class="auth-input-group">
|
||||
<label>密码</label>
|
||||
<input type="password" id="registerPassword" placeholder="请输入密码(6-20字符)" autocomplete="new-password">
|
||||
</div>
|
||||
<div class="auth-input-group">
|
||||
<label>确认密码</label>
|
||||
<input type="password" id="registerPasswordConfirm" placeholder="请再次输入密码" autocomplete="new-password">
|
||||
</div>
|
||||
<button class="auth-submit-btn" id="registerSubmitBtn">注册</button>
|
||||
</div>
|
||||
|
||||
<div class="auth-footer">
|
||||
<p>已有账号?<span class="auth-link" id="goToLogin">立即登录</span></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
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 = `
|
||||
<div class="limit-dialog" id="limitDialog">
|
||||
<div class="limit-dialog-content">
|
||||
<div class="limit-dialog-icon">⚠️</div>
|
||||
<div class="limit-dialog-title">${title}</div>
|
||||
<div class="limit-dialog-message">${message}</div>
|
||||
<div class="limit-dialog-actions">
|
||||
${showLoginBtn ? '<button class="limit-dialog-btn login" id="limitLoginBtn">登录</button>' : ''}
|
||||
<button class="limit-dialog-btn cancel" id="limitCancelBtn">知道了</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 添加到页面
|
||||
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); // 获取所有
|
||||
@@ -1222,11 +1745,36 @@ 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);
|
||||
}
|
||||
|
||||
// 创建新对话并设置智能体
|
||||
createNewConversation();
|
||||
currentConversation.agentId = agentId;
|
||||
currentConversation.title = currentAgent.name;
|
||||
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();
|
||||
@@ -1895,6 +2443,12 @@ function togglePinConversation(convId) {
|
||||
|
||||
// 创建新对话
|
||||
function createNewConversation() {
|
||||
// 检查未登录用户的对话限制
|
||||
if (!currentUser && !canCreateNewChat()) {
|
||||
showLimitDialog('chat_session');
|
||||
return;
|
||||
}
|
||||
|
||||
const newConv = {
|
||||
id: Date.now().toString(),
|
||||
title: '新对话',
|
||||
@@ -1905,6 +2459,10 @@ function createNewConversation() {
|
||||
|
||||
conversations.unshift(newConv);
|
||||
saveConversations();
|
||||
|
||||
// 增加对话会话计数(未登录用户)
|
||||
incrementChatSession();
|
||||
|
||||
openConversation(newConv.id);
|
||||
}
|
||||
|
||||
@@ -2161,6 +2719,23 @@ async function sendMessage() {
|
||||
|
||||
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';
|
||||
@@ -2181,6 +2756,15 @@ async function sendMessage() {
|
||||
currentConversation.updatedAt = Date.now();
|
||||
saveConversations();
|
||||
|
||||
// 增加消息计数(未登录用户)
|
||||
if (!currentUser) {
|
||||
if (currentConversation.agentId) {
|
||||
incrementAgentMessage();
|
||||
} else {
|
||||
incrementChatMessage();
|
||||
}
|
||||
}
|
||||
|
||||
// 发送新消息时启用自动滚动
|
||||
autoScrollEnabled = true;
|
||||
|
||||
|
||||
@@ -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=3.0.0">
|
||||
<link rel="stylesheet" href="style.css?v=3.3.0">
|
||||
<link rel="manifest" href="manifest.json">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="marked.min.js?v=3.0.0"></script>
|
||||
<script src="app.js?v=3.0.0"></script>
|
||||
<script src="marked.min.js?v=3.3.0"></script>
|
||||
<script src="app.js?v=3.3.0"></script>
|
||||
</body>
|
||||
</html>
|
||||
315
www/style.css
315
www/style.css
@@ -689,6 +689,321 @@ body {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* ==================== 登录注册页面 ==================== */
|
||||
|
||||
.auth-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
height: 100dvh;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.auth-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
background: linear-gradient(135deg, var(--primary) 0%, #764ba2 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.auth-header h1 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.auth-content {
|
||||
flex: 1;
|
||||
padding: 24px 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.auth-form {
|
||||
background: white;
|
||||
padding: 24px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.auth-input-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.auth-input-group label {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--text-color);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.auth-input-group input {
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
border: 2px solid var(--border-color);
|
||||
border-radius: 10px;
|
||||
font-size: 16px;
|
||||
outline: none;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.auth-input-group input:focus {
|
||||
border-color: var(--primary);
|
||||
}
|
||||
|
||||
.auth-submit-btn {
|
||||
width: 100%;
|
||||
padding: 14px;
|
||||
background: linear-gradient(135deg, var(--primary) 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.auth-submit-btn:hover {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.auth-submit-btn:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.auth-footer {
|
||||
text-align: center;
|
||||
padding: 24px;
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
.auth-link {
|
||||
color: var(--primary);
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.auth-link:hover {
|
||||
color: #5a67d8;
|
||||
}
|
||||
|
||||
/* ==================== 我的页面 ==================== */
|
||||
|
||||
.profile-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 24px;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.profile-avatar {
|
||||
font-size: 48px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.profile-name {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--text-color);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.profile-status {
|
||||
font-size: 14px;
|
||||
color: #22c55e;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.profile-status.guest {
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
.profile-login-buttons {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.login-btn, .register-btn, .logout-btn {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background: linear-gradient(135deg, var(--primary) 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.login-btn:hover, .register-btn:hover, .logout-btn:hover {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.register-btn {
|
||||
background: white;
|
||||
border: 2px solid var(--primary);
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.logout-btn {
|
||||
background: #fee2e2;
|
||||
color: #e53e3e;
|
||||
}
|
||||
|
||||
.skip-btn {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
background: transparent;
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-light);
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.skip-btn:hover {
|
||||
background: rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
/* ==================== 配额显示 ==================== */
|
||||
|
||||
.quota-card {
|
||||
background: white;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 16px;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.quota-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--text-color);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.quota-items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.quota-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.quota-label {
|
||||
font-size: 14px;
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
.quota-value {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.quota-tip {
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
color: #f59e0b;
|
||||
margin-top: 12px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
/* ==================== 限制提示对话框 ==================== */
|
||||
|
||||
.limit-dialog {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0,0,0,0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 2000;
|
||||
}
|
||||
|
||||
.limit-dialog-content {
|
||||
background: white;
|
||||
padding: 24px;
|
||||
border-radius: 16px;
|
||||
max-width: 300px;
|
||||
text-align: center;
|
||||
animation: dialogSlideUp 0.2s ease;
|
||||
}
|
||||
|
||||
@keyframes dialogSlideUp {
|
||||
from { transform: translateY(20px); opacity: 0; }
|
||||
to { transform: translateY(0); opacity: 1; }
|
||||
}
|
||||
|
||||
.limit-dialog-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.limit-dialog-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--text-color);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.limit-dialog-message {
|
||||
font-size: 14px;
|
||||
color: var(--text-light);
|
||||
line-height: 1.6;
|
||||
margin-bottom: 20px;
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
.limit-dialog-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.limit-dialog-btn {
|
||||
flex: 1;
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.limit-dialog-btn.login {
|
||||
background: linear-gradient(135deg, var(--primary) 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.limit-dialog-btn.login:hover {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.limit-dialog-btn.cancel {
|
||||
background: white;
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
.limit-dialog-btn.cancel:hover {
|
||||
background: var(--bg-color);
|
||||
}
|
||||
|
||||
/* 最近使用的智能体列表 */
|
||||
.recent-agents-list {
|
||||
display: flex;
|
||||
|
||||
Reference in New Issue
Block a user