812 lines
35 KiB
JavaScript
812 lines
35 KiB
JavaScript
function i18n(key) {
|
|
return lang[selectedLang][key] || key;
|
|
}
|
|
|
|
// 显示用户中心特定部分
|
|
async function showUserCenterSection(section) {
|
|
try {
|
|
const userInfo = await fetchUserInfo();
|
|
if (!userInfo) {
|
|
alert(lang[selectedLang].pleaseLogin);
|
|
window.location.href = 'login.html'; // 重定向到登录页面
|
|
return;
|
|
}
|
|
|
|
// 检查邮箱验证状态
|
|
const emailVerified = userInfo.email_verified === 'True' || userInfo.email_verified === true;
|
|
console.log('Email Verified:', emailVerified); // 打印邮箱验证状态
|
|
if (!emailVerified && section !== 'profile') {
|
|
alert(lang[selectedLang].pleaseVerifyEmail);
|
|
section = 'profile'; // 如果邮箱未验证,强制显示个人资料页面
|
|
// showUserCenterSection('profile'); // 跳转到个人资料页面
|
|
return;
|
|
}
|
|
|
|
const dashboardContent = document.getElementById('dashboardContent');
|
|
const usersContent = document.getElementById('usersContent');
|
|
const chatContent = document.getElementById('chatContent');
|
|
const featureContent = document.getElementById('featureContent');
|
|
const userCenterContent = document.getElementById('userCenterContent');
|
|
const pageTitle = document.getElementById('pageTitle');
|
|
const showApiDocs = document.getElementById('showApiDocs');
|
|
const apiDocsContent = document.getElementById('apiDocsContent');
|
|
|
|
|
|
if (apiDocsContent) apiDocsContent.style.display = 'none';
|
|
if (dashboardContent) dashboardContent.style.display = 'none';
|
|
if (usersContent) usersContent.style.display = 'none';
|
|
if (chatContent) chatContent.style.display = 'none';
|
|
if (featureContent) featureContent.style.display = 'none';
|
|
if (userCenterContent) userCenterContent.style.display = 'block';
|
|
if (showApiDocs) showApiDocs.style.display = 'none';
|
|
|
|
// 根据选择的部分设置相应的标题
|
|
if (pageTitle) {
|
|
switch(section) {
|
|
case 'profile':
|
|
pageTitle.innerText = i18n('personalInfo');
|
|
break;
|
|
case 'apiKey':
|
|
pageTitle.innerText = i18n('apiKeys');
|
|
break;
|
|
case 'apiUsage':
|
|
pageTitle.innerText = i18n('modelCalls');
|
|
break;
|
|
default:
|
|
pageTitle.innerText = i18n('userCenter');
|
|
}
|
|
}
|
|
|
|
|
|
const navButtons = document.querySelectorAll('.user-center-nav-btn');
|
|
const sections = document.querySelectorAll('.user-center-section');
|
|
|
|
navButtons.forEach(btn => btn.classList.remove('active'));
|
|
sections.forEach(sec => sec.classList.remove('active'));
|
|
|
|
const targetButton = document.querySelector(`.user-center-nav-btn[data-target="${section}"]`);
|
|
const targetSection = document.getElementById(`${section}Section`);
|
|
|
|
if (targetButton) targetButton.classList.add('active');
|
|
if (targetSection) targetSection.classList.add('active');
|
|
|
|
if (section === 'profile') {
|
|
loadUserProfile();
|
|
} else if (section === 'apiKey') {
|
|
loadApiKeys();
|
|
} else if (section === 'apiUsage') {
|
|
loadapiUsage();
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Error in showFeature:', error);
|
|
alert(lang[selectedLang].errorGettingUserInfo);
|
|
}
|
|
}
|
|
// 将 showUserCenterSection 函数添加到全局作用域
|
|
window.showUserCenterSection = showUserCenterSection;
|
|
|
|
const BASE_URL = 'https://user.obscura.work/user';
|
|
|
|
|
|
function updateAllAvatars(avatarUrl) {
|
|
const avatars = document.querySelectorAll('.user-avatar, #currentAvatar');
|
|
avatars.forEach(avatar => {
|
|
avatar.src = avatarUrl || 'picture/cn__03041_.png';
|
|
});
|
|
}
|
|
|
|
|
|
// 加载用户个人资料
|
|
async function loadUserProfile() {
|
|
try {
|
|
const response = await fetch(`${BASE_URL}/me`, {
|
|
headers: {
|
|
'Authorization': `Bearer ${localStorage.getItem('access_token')}`
|
|
}
|
|
});
|
|
if (response.ok) {
|
|
const userData = await response.json();
|
|
const profileSection = document.getElementById('profileSection');
|
|
updateAllAvatars(userData.avatar)
|
|
profileSection.innerHTML = `
|
|
<div class="profile-container">
|
|
<div class="profile-info">
|
|
<h3>${i18n("basicInfo")}</h3>
|
|
<form id="profileForm">
|
|
<div class="form-group">
|
|
<label for="username">${i18n("username")}</label>
|
|
<input type="text" id="username" value="${userData.username}" readonly>
|
|
<p class="field-description">${i18n("usernameDescription")}</p>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="email">${i18n("email")}</label>
|
|
<input type="email" id="email" value="${userData.email}">
|
|
<p class="field-description">${i18n("emailDescription")}</p>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="phone_number">${i18n("phoneNumber")}</label>
|
|
<input type="tel" id="phone_number" value="${userData.phone_number}">
|
|
</div>
|
|
<button type="submit" class="btn-update">${i18n("updateProfile")}</button>
|
|
</form>
|
|
</div>
|
|
<div class="profile-picture">
|
|
<img src="${userData.avatar || 'picture/cn__03041_.png'}" alt="用户头像" id="currentAvatar">
|
|
<button class="btn-update" onclick="openAvatarModal()">${i18n("editAvatar")}</button>
|
|
</div>
|
|
</div>
|
|
<div class="verification-card">
|
|
<h3>${i18n("userVerification")}</h3>
|
|
<div class="verification-content">
|
|
<p>${i18n("emailVerification")}<span id="emailStatus">${userData.email_verified ? i18n("verified") : i18n("unverified")}</span></p>
|
|
${userData.email_verified ? '' : `
|
|
<button type="button" id="sendVerificationEmail" class="btn-verify">${i18n("sendVerificationEmail")}</button>
|
|
<div id="verificationCodeSection" style="display: none;">
|
|
<input type="text" id="verificationCode" name="email_verification_code" autocomplete="off" placeholder="${i18n("enterVerificationCode")}">
|
|
<button type="button" id="submitVerificationCode" class="btn-verify">${i18n("submitVerificationCode")}</button>
|
|
</div>
|
|
`}
|
|
</div>
|
|
<div class="verification-content" style="margin-top: 15px;">
|
|
<p>${i18n("phoneVerification")}<span id="emailStatus">${userData.phone_verified ? i18n("verified") : i18n("unverified")}</span></p>
|
|
${userData.phone_verified ? '' : `
|
|
<button type="button" id="sendPhoneVerification" class="btn-verify">${i18n("sendPhoneVerification")}</button>
|
|
<div id="phoneVerificationCodeSection" style="display: none;">
|
|
<input type="text" id="phoneVerificationCode" placeholder="${i18n("enterPhoneVerificationCode")}">
|
|
<button type="button" id="submitPhoneVerificationCode" class="btn-verify">${i18n("submitPhoneVerificationCode")}</button>
|
|
</div>
|
|
`}
|
|
</div>
|
|
</div>
|
|
`;
|
|
document.getElementById('profileForm').addEventListener('submit', updateProfile);
|
|
|
|
if (!userData.email_verified) {
|
|
document.getElementById('sendVerificationEmail').addEventListener('click', sendVerificationEmail);
|
|
document.getElementById('submitVerificationCode').addEventListener('click', submitVerificationCode);
|
|
}
|
|
if (!userData.phone_verified) {
|
|
document.getElementById('sendPhoneVerification').addEventListener('click', sendPhoneVerification);
|
|
document.getElementById('submitPhoneVerificationCode').addEventListener('click', submitPhoneVerificationCode);
|
|
}
|
|
} else {
|
|
console.error(i18n('loadProfileFailed'));
|
|
}
|
|
} catch (error) {
|
|
console.error(i18n('loadProfileError'), error);
|
|
}
|
|
}
|
|
|
|
// 提交验证码
|
|
async function sendVerificationEmail() {
|
|
const sendButton = document.getElementById('sendVerificationEmail');
|
|
if (sendButton.disabled) {
|
|
return; // 如果按钮已经被禁用,直接返回
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`${BASE_URL}/send-verification-email`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bearer ${localStorage.getItem('access_token')}`
|
|
}
|
|
});
|
|
if (response.ok) {
|
|
alert(i18n('verificationEmailSent'));
|
|
document.getElementById('verificationCodeSection').style.display = 'block';
|
|
|
|
// 禁用按钮并开始倒计时
|
|
sendButton.disabled = true;
|
|
let countdown = 60;
|
|
sendButton.textContent = `${i18n('resend')} (${countdown}s)`;
|
|
|
|
const timer = setInterval(() => {
|
|
countdown--;
|
|
if (countdown <= 0) {
|
|
clearInterval(timer);
|
|
sendButton.disabled = false;
|
|
sendButton.textContent = i18n('sendVerificationEmail');
|
|
} else {
|
|
sendButton.textContent = `${i18n('resend')} (${countdown}s)`;
|
|
}
|
|
}, 1000);
|
|
} else {
|
|
const errorData = await response.json();
|
|
alert(`${i18n('sendVerificationEmailFailed')}${errorData.detail || i18n('unknownError')}`);
|
|
}
|
|
} catch (error) {
|
|
console.error(i18n('sendVerificationEmailError'), error);
|
|
alert(i18n('sendVerificationEmailError'));
|
|
}
|
|
}
|
|
|
|
async function submitVerificationCode() {
|
|
const verificationCode = document.getElementById('verificationCode').value;
|
|
console.log(i18n('submittingVerificationCode'), verificationCode);
|
|
try {
|
|
const response = await fetch(`${BASE_URL}/verify-email`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bearer ${localStorage.getItem('access_token')}`,
|
|
'Content-Type': 'application/x-www-form-urlencoded'
|
|
},
|
|
body: `verification_code=${encodeURIComponent(verificationCode)}`
|
|
});
|
|
if (response.ok) {
|
|
alert(i18n('emailVerificationSuccess'));
|
|
loadUserProfile();
|
|
} else {
|
|
const errorData = await response.json();
|
|
console.error('Error response:', errorData);
|
|
alert(`${i18n('verificationFailed')}${errorData.detail || i18n('unknownError')}`);
|
|
}
|
|
} catch (error) {
|
|
console.error(i18n('submitVerificationCodeError'), error);
|
|
alert(i18n('submitVerificationCodeError'));
|
|
}
|
|
}
|
|
|
|
|
|
// 新增发送手机验证短信的函数
|
|
async function sendPhoneVerification() {
|
|
const sendButton = document.getElementById('sendPhoneVerification');
|
|
if (sendButton.disabled) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`${BASE_URL}/send-phone-verification`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bearer ${localStorage.getItem('access_token')}`
|
|
}
|
|
});
|
|
if (response.ok) {
|
|
alert(i18n('verificationSMSSent'));
|
|
document.getElementById('phoneVerificationCodeSection').style.display = 'block';
|
|
|
|
sendButton.disabled = true;
|
|
let countdown = 60;
|
|
sendButton.textContent = `${i18n('resend')} (${countdown}s)`;
|
|
|
|
const timer = setInterval(() => {
|
|
countdown--;
|
|
if (countdown <= 0) {
|
|
clearInterval(timer);
|
|
sendButton.disabled = false;
|
|
sendButton.textContent = i18n('sendPhoneVerification');
|
|
} else {
|
|
sendButton.textContent = `${i18n('resend')} (${countdown}s)`;
|
|
}
|
|
}, 1000);
|
|
} else {
|
|
const errorData = await response.json();
|
|
alert(`${i18n('sendVerificationSMSFailed')}${errorData.detail || i18n('unknownError')}`);
|
|
}
|
|
} catch (error) {
|
|
console.error(i18n('sendVerificationSMSError'), error);
|
|
alert(i18n('sendVerificationSMSError'));
|
|
}
|
|
}
|
|
|
|
// 新增提交手机验证码的函数
|
|
async function submitPhoneVerificationCode() {
|
|
const verificationCode = document.getElementById('phoneVerificationCode').value;
|
|
try {
|
|
const response = await fetch(`${BASE_URL}/verify-phone`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bearer ${localStorage.getItem('access_token')}`,
|
|
'Content-Type': 'application/x-www-form-urlencoded'
|
|
},
|
|
body: `verification_code=${encodeURIComponent(verificationCode)}`
|
|
});
|
|
if (response.ok) {
|
|
alert(i18n('phoneVerificationSuccess'));
|
|
loadUserProfile(); // 重新加载用户资料以更新验证状态
|
|
} else {
|
|
const errorData = await response.json();
|
|
alert(`${i18n('verificationFailed')}${errorData.detail || i18n('unknownError')}`);
|
|
}
|
|
} catch (error) {
|
|
console.error(i18n('submitPhoneVerificationCodeError'), error);
|
|
alert(i18n('submitPhoneVerificationCodeError'));
|
|
}
|
|
}
|
|
|
|
|
|
// / 打开头像选择模态框
|
|
function openAvatarModal() {
|
|
const modal = document.createElement('div');
|
|
modal.className = 'avatar-modal';
|
|
modal.innerHTML = `
|
|
<div class="avatar-modal-content">
|
|
<h3>${i18n("selectAvatar")}</h3>
|
|
<div id="avatarOptions"></div>
|
|
<button onclick="closeAvatarModal()">${i18n("close")}</button>
|
|
</div>
|
|
`;
|
|
document.body.appendChild(modal);
|
|
loadAvatarOptions();
|
|
}
|
|
|
|
// 加载头像选项
|
|
async function loadAvatarOptions() {
|
|
try {
|
|
const avatars = [
|
|
'picture/cn__02504_.png',
|
|
'picture/cn__02505_.png',
|
|
'picture/cn__02506_.png',
|
|
'picture/cn__02507_.png',
|
|
'picture/cn__02510_.png',
|
|
'picture/cn__02511_.png',
|
|
'picture/cn__02512_.png',
|
|
'picture/cn__03018_.png',
|
|
'picture/cn__03040_.png',
|
|
'picture/cn__03041_.png',
|
|
'picture/cn__03042_.png',
|
|
'picture/cn__03044_.png',
|
|
'picture/cn__03059_.png'
|
|
];
|
|
const avatarOptions = document.getElementById('avatarOptions');
|
|
avatarOptions.innerHTML = avatars.map(avatar => `
|
|
<img src="${avatar}" alt="头像选项" onclick="selectAvatar('${avatar}')" class="avatar-option">
|
|
`).join('');
|
|
} catch (error) {
|
|
console.error(i18n('loadAvatarOptionsError'), error);
|
|
}
|
|
}
|
|
|
|
// 选择头像
|
|
function selectAvatar(avatarUrl) {
|
|
document.getElementById('currentAvatar').src = avatarUrl;
|
|
document.getElementById('userAvatar').src = avatarUrl;
|
|
closeAvatarModal();
|
|
updateUserAvatar(avatarUrl); // 添加这行来保存头像更改
|
|
}
|
|
|
|
async function updateUserAvatar(avatarUrl) {
|
|
try {
|
|
const response = await fetch(`${BASE_URL}/me`, {
|
|
method: 'PUT',
|
|
headers: {
|
|
'Authorization': `Bearer ${localStorage.getItem('access_token')}`,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({ avatar: avatarUrl })
|
|
});
|
|
if (response.ok) {
|
|
console.log(i18n('avatarUpdateSuccess'));
|
|
updateAllAvatars(avatarUrl);
|
|
} else {
|
|
const errorData = await response.json();
|
|
console.error(i18n('avatarUpdateFailed'), errorData);
|
|
alert(`${i18n('avatarUpdateFailed')}${errorData.detail || i18n('unknownError')}`);
|
|
}
|
|
} catch (error) {
|
|
console.error(i18n('avatarUpdateError'), error);
|
|
alert(i18n('avatarUpdateError'));
|
|
}
|
|
}
|
|
// 关闭头像选择模态框
|
|
function closeAvatarModal() {
|
|
const modal = document.querySelector('.avatar-modal');
|
|
if (modal) {
|
|
modal.remove();
|
|
}
|
|
}
|
|
|
|
async function updateProfile(event) {
|
|
event.preventDefault();
|
|
const email = document.getElementById('email').value;
|
|
const phone_number = document.getElementById('phone_number').value;
|
|
const avatar = document.getElementById('currentAvatar').src;
|
|
try {
|
|
const response = await fetch(`${BASE_URL}/me`, {
|
|
method: 'PUT',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${localStorage.getItem('access_token')}`
|
|
},
|
|
body: JSON.stringify({ email, phone_number, avatar })
|
|
});
|
|
if (response.ok) {
|
|
alert(i18n('profileUpdateSuccess'));
|
|
} else {
|
|
const errorData = await response.json();
|
|
alert(`${i18n('profileUpdateFailed')}${errorData.detail || i18n('unknownError')}`);
|
|
}
|
|
} catch (error) {
|
|
console.error(i18n('profileUpdateError'), error);
|
|
alert(i18n('profileUpdateError'));
|
|
}
|
|
}
|
|
|
|
|
|
// 遮蔽 API 密钥
|
|
function maskApiKey(key) {
|
|
if (!key) return i18n('invalidApiKey');
|
|
return key.substring(0, 4) + '*'.repeat(Math.max(0, key.length - 8)) + key.substring(Math.max(0, key.length - 4));
|
|
}
|
|
|
|
// 加载 API 密钥
|
|
async function loadApiKeys() {
|
|
try {
|
|
const response = await fetch(`${BASE_URL}/api-keys`, {
|
|
headers: {
|
|
'Authorization': `Bearer ${localStorage.getItem('access_token')}`,
|
|
'Cache-Control': 'no-cache'
|
|
}
|
|
});
|
|
if (response.ok) {
|
|
const apiKeys = await response.json();
|
|
const apiKeyContent = document.getElementById('apiKeyContent');
|
|
apiKeyContent.innerHTML = `
|
|
<table class="api-key-table">
|
|
<thead>
|
|
<tr>
|
|
<th></th>
|
|
<th>${i18n("apiKeyTable")}</th>
|
|
<th>${i18n("createdAt")}</th>
|
|
<th>${i18n("expiresAt")}</th>
|
|
<th>${i18n("operation")}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="apiKeyTableBody">
|
|
${apiKeys.map((key, index) => {
|
|
const actualKey = key.key.replace('api_key:', '');
|
|
return `
|
|
<tr>
|
|
<td>
|
|
<i class="fas fa-eye toggle-visibility" onclick="toggleApiKeyVisibility(${index}, '${actualKey}')"></i>
|
|
</td>
|
|
<td>
|
|
<span id="apiKey${index}" class="api-key-value" onclick="copyToClipboard('${actualKey}')">${maskApiKey(actualKey)}</span>
|
|
</td>
|
|
<td>${new Date(key.created_at).toLocaleString()}</td>
|
|
<td>${new Date(key.expires_at).toLocaleString()}</td>
|
|
<td>
|
|
<button class="btn-delete" onclick="deleteApiKey('${actualKey}')">
|
|
<i class="fas fa-trash-alt"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
`;
|
|
}).join('')}
|
|
</tbody>
|
|
</table>
|
|
<button class="btn-generate-key" onclick="generateNewApiKey()">${i18n("createNewApiKey")}</button>
|
|
<button class="btn-refresh" onclick="refreshApiKeys()">${i18n("refreshList")}</button>
|
|
`;
|
|
} else {
|
|
console.error(i18n('loadApiKeysError'), response.status, response.statusText);
|
|
const apiKeyContent = document.getElementById('apiKeyContent');
|
|
apiKeyContent.innerHTML = `<p>${i18n('loadApiKeysFailed')}</p>`;
|
|
}
|
|
} catch (error) {
|
|
console.error(i18n('loadApiKeysError'), error);
|
|
const apiKeyContent = document.getElementById('apiKeyContent');
|
|
apiKeyContent.innerHTML = `<p>${i18n('loadApiKeysFailed')}</p>`;
|
|
}
|
|
}
|
|
|
|
// 切换 API 密钥可见性
|
|
function toggleApiKeyVisibility(index, fullKey) {
|
|
const keyElement = document.getElementById(`apiKey${index}`);
|
|
if (keyElement.textContent === fullKey) {
|
|
keyElement.textContent = maskApiKey(fullKey);
|
|
} else {
|
|
keyElement.textContent = fullKey;
|
|
}
|
|
}
|
|
|
|
// 复制到剪贴板
|
|
function copyToClipboard(text) {
|
|
navigator.clipboard.writeText(text).then(() => {
|
|
alert(i18n('apiKeyCopied'));
|
|
}).catch(err => {
|
|
console.error(i18n('copyTextFailed'), err);
|
|
});
|
|
}
|
|
|
|
// 生成新的 API 密钥
|
|
async function generateNewApiKey() {
|
|
try {
|
|
const response = await fetch(`${BASE_URL}/api-keys`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bearer ${localStorage.getItem('access_token')}`
|
|
}
|
|
});
|
|
if (response.ok) {
|
|
const newKey = await response.json();
|
|
alert(`${i18n('newApiKeyGenerated')}${newKey.api_key}\n${i18n('expirationTime')}${new Date(newKey.expires_at).toLocaleString()}`);
|
|
loadApiKeys(); // 重新加载 API 密钥列表
|
|
} else {
|
|
const errorData = await response.json();
|
|
alert(`${i18n('generateNewApiKeyFailed')}${errorData.detail || i18n('unknownError')}`);
|
|
}
|
|
} catch (error) {
|
|
console.error(i18n('generateNewApiKeyError'), error);
|
|
alert(i18n('generateNewApiKeyFailed'));
|
|
}
|
|
}
|
|
|
|
// 修改 deleteApiKey 函数
|
|
async function deleteApiKey(keyId) {
|
|
const confirmed = confirm(i18n('confirmDeleteApiKey'));
|
|
if (confirmed) {
|
|
try {
|
|
const encodedKeyId = encodeURIComponent(keyId);
|
|
const response = await fetch(`${BASE_URL}/api-keys/${encodedKeyId}`, {
|
|
method: 'DELETE',
|
|
headers: {
|
|
'Authorization': `Bearer ${localStorage.getItem('access_token')}`
|
|
}
|
|
});
|
|
const data = await response.json();
|
|
if (response.ok) {
|
|
alert(data.message);
|
|
} else {
|
|
const errorData = await response.json();
|
|
alert(`${i18n('deleteApiKeyFailed')}${errorData.detail || i18n('unknownError')}`);
|
|
}
|
|
if (data.action === 'clear_api_info') {
|
|
await loadApiKeys(); // 重新加载API密钥列表
|
|
}
|
|
} catch (error) {
|
|
console.error(i18n('deleteApiKeyError'), error);
|
|
alert(i18n('deleteApiKeyFailed'));
|
|
} finally {
|
|
await loadApiKeys(); // 无论成功与否,都重新加载API密钥列表
|
|
}
|
|
}
|
|
}
|
|
|
|
// 新增清除缓存并重新加载的函数
|
|
// 确保 refreshApiKeys 函数被正确定义
|
|
async function refreshApiKeys() {
|
|
try {
|
|
await fetch(`${BASE_URL}/clear-cache`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bearer ${localStorage.getItem('access_token')}`
|
|
}
|
|
});
|
|
console.log(i18n('cacheCleared'));
|
|
} catch (error) {
|
|
console.error(i18n('clearCacheFailed'), error);
|
|
} finally {
|
|
await loadApiKeys();
|
|
console.log(i18n('apiKeyListRefreshed'));
|
|
}
|
|
}
|
|
|
|
// 加载模型调用信息
|
|
async function loadapiUsage() {
|
|
try {
|
|
const response = await fetch(`${BASE_URL}/api-usage`, {
|
|
headers: {
|
|
'Authorization': `Bearer ${localStorage.getItem('access_token')}`
|
|
}
|
|
});
|
|
if (response.ok) {
|
|
const usageData = await response.json();
|
|
const apiUsageContent = document.getElementById('apiUsageContent');
|
|
|
|
apiUsageContent.innerHTML = `
|
|
<div class="usage-tabs">
|
|
<h3 id="apiUsageBtn" class="tab-btn active" onclick="showApiUsage()">${i18n("apiUsage")}</h3>
|
|
<h3 id="modelUsageBtn" class="tab-btn" onclick="showModelUsage()">${i18n("modelUsage")}</h3>
|
|
</div>
|
|
<div id="apiUsageTable" class="tab-content active">
|
|
<table class="rate-limit-table">
|
|
<thead>
|
|
<tr>
|
|
<th></th>
|
|
<th>API KEY</th>
|
|
<th>${i18n("tokensUsed")}</th>
|
|
<th>${i18n("totalTokens")}</th>
|
|
<th>${i18n("lastUsedAt")}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="apiUsageTableBody">
|
|
${Object.keys(usageData).length === 0 ?
|
|
`<tr><td colspan="5">${i18n("noUsageRecord")}</td></tr>` :
|
|
Object.entries(usageData).map(([key, usage], index) => `
|
|
<tr>
|
|
<td>
|
|
<i class="fas fa-eye toggle-visibility" onclick="toggleApiKeyVisibility(${index}, '${key}')"></i>
|
|
</td>
|
|
<td>
|
|
<span id="apiKey${index}" class="api-key-value">${maskApiKey(key)}</span>
|
|
</td>
|
|
<td>${usage.tokens_used || 0} token</td>
|
|
<td>${usage.total_tokens || 0} token</td>
|
|
<td>${usage.last_used_at ? new Date(usage.last_used_at).toLocaleString() : i18n("unused")}</td>
|
|
</tr>
|
|
`).join('')
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div id="modelUsageTable" class="tab-content">
|
|
<p>加载中...</p>
|
|
</div>
|
|
`;
|
|
|
|
// 初始显示API使用情况
|
|
showApiUsage();
|
|
} else {
|
|
console.error(i18n('loadModelCallInfoError'), response.status, response.statusText);
|
|
const apiUsageContent = document.getElementById('apiUsageContent');
|
|
apiUsageContent.innerHTML = `<p>${i18n('loadModelCallInfoFailed')}</p>`;
|
|
}
|
|
} catch (error) {
|
|
console.error(i18n('loadModelCallInfoError'), error);
|
|
const apiUsageContent = document.getElementById('apiUsageContent');
|
|
apiUsageContent.innerHTML = `
|
|
<p>${i18n('loadModelCallInfoFailed')}</p>
|
|
<button onclick="loadapiUsage()">${i18n('reload')}</button>
|
|
`;
|
|
}
|
|
}
|
|
|
|
// 显示 API 使用情况
|
|
function showApiUsage() {
|
|
document.getElementById('apiUsageBtn').classList.add('active');
|
|
document.getElementById('modelUsageBtn').classList.remove('active');
|
|
document.getElementById('apiUsageTable').classList.add('active');
|
|
document.getElementById('modelUsageTable').classList.remove('active');
|
|
|
|
// 添加动画效果
|
|
document.getElementById('apiUsageTable').style.animation = 'none';
|
|
setTimeout(() => {
|
|
document.getElementById('apiUsageTable').style.animation = 'fadeIn 0.5s ease-in-out';
|
|
}, 10);
|
|
|
|
}
|
|
|
|
// 显示模型使用情况
|
|
async function showModelUsage() {
|
|
document.getElementById('apiUsageBtn').classList.remove('active');
|
|
document.getElementById('modelUsageBtn').classList.add('active');
|
|
document.getElementById('apiUsageTable').classList.remove('active');
|
|
document.getElementById('modelUsageTable').classList.add('active');
|
|
// 添加动画效果
|
|
document.getElementById('modelUsageTable').style.animation = 'none';
|
|
setTimeout(() => {
|
|
document.getElementById('modelUsageTable').style.animation = 'fadeIn 0.5s ease-in-out';
|
|
}, 10);
|
|
// 加载模型使用情况数据
|
|
try {
|
|
const response = await fetch(`${BASE_URL}/model-usage`, {
|
|
headers: {
|
|
'Authorization': `Bearer ${localStorage.getItem('access_token')}`
|
|
}
|
|
});
|
|
if (response.ok) {
|
|
const modelUsageData = await response.json();
|
|
const modelUsageTable = document.getElementById('modelUsageTable');
|
|
|
|
modelUsageTable.innerHTML = `
|
|
<table class="rate-limit-table">
|
|
<thead>
|
|
<tr>
|
|
<th>${i18n("modelName")}</th>
|
|
<th>${i18n("lastCallTime")}</th>
|
|
<th>${i18n("usage")}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
${Object.entries(modelUsageData).map(([model, usage]) => `
|
|
<tr>
|
|
<td>${model}</td>
|
|
<td>${usage.last_called_at ? new Date(usage.last_called_at).toLocaleString() : '未使用'}</td>
|
|
<td>${usage.calls} token</td>
|
|
</tr>
|
|
`).join('')}
|
|
</tbody>
|
|
</table>
|
|
`;
|
|
} else {
|
|
console.error(i18n('loadModelUsageFailed'), response.status, response.statusText);
|
|
}
|
|
} catch (error) {
|
|
console.error(i18n('loadModelUsageError'), error);
|
|
document.getElementById('modelUsageTable').innerHTML = `
|
|
<p>${i18n('loadModelUsageFailed')}</p>
|
|
`;
|
|
}
|
|
}
|
|
|
|
// 初始化用户中心
|
|
function initUserCenter() {
|
|
const navButtons = document.querySelectorAll('.user-center-nav-btn');
|
|
navButtons.forEach(button => {
|
|
button.addEventListener('click', () => {
|
|
const target = button.getAttribute('data-target');
|
|
showUserCenterSection(target);
|
|
});
|
|
});
|
|
|
|
// 加载用户信息并更新头像
|
|
fetch(`${BASE_URL}/me`, {
|
|
headers: {
|
|
'Authorization': `Bearer ${localStorage.getItem('access_token')}`
|
|
}
|
|
})
|
|
.then(response => response.json())
|
|
.then(userData => {
|
|
updateAllAvatars(userData.avatar);
|
|
if (!userData.email_verified) {
|
|
const sendVerificationEmailBtn = document.getElementById('sendVerificationEmail');
|
|
const submitVerificationCodeBtn = document.getElementById('submitVerificationCode');
|
|
if (sendVerificationEmailBtn) {
|
|
sendVerificationEmailBtn.addEventListener('click', sendVerificationEmail);
|
|
}
|
|
if (submitVerificationCodeBtn) {
|
|
submitVerificationCodeBtn.addEventListener('click', submitVerificationCode);
|
|
}
|
|
}
|
|
})
|
|
.catch(error => console.error(i18n('getUserInfoFailed'), error));
|
|
}
|
|
|
|
// 页面加载完成后初始化用户中心
|
|
window.addEventListener('load', initUserCenter);
|
|
|
|
// 将 showUserCenterSection 函数添加到全局作用域
|
|
window.showUserCenterSection = showUserCenterSection;
|
|
window.generateNewApiKey = generateNewApiKey;
|
|
window.deleteApiKey = deleteApiKey;
|
|
window.copyToClipboard = copyToClipboard;
|
|
window.refreshApiKeys = refreshApiKeys;
|
|
window.loadapiUsage = loadapiUsage;
|
|
window.showApiUsage = showApiUsage;
|
|
window.showModelUsage = showModelUsage;
|
|
window.toggleApiKeyVisibility = toggleApiKeyVisibility;
|
|
window.openAvatarModal = openAvatarModal;
|
|
window.selectAvatar = selectAvatar;
|
|
window.closeAvatarModal = closeAvatarModal;
|
|
window.showUserCenterSection = showUserCenterSection;
|
|
|
|
|
|
window.sendPhoneVerification = sendPhoneVerification;
|
|
window.submitPhoneVerificationCode = submitPhoneVerificationCode;
|
|
window.sendVerificationEmail = sendVerificationEmail;
|
|
window.submitVerificationCode = submitVerificationCode;
|
|
|
|
|
|
// 获取用户信息的函数
|
|
async function fetchUserInfo() {
|
|
try {
|
|
const token = localStorage.getItem('access_token');
|
|
if (!token) {
|
|
console.error(i18n('noAccessTokenFound'));
|
|
return null;
|
|
}
|
|
|
|
const response = await fetch(`${BASE_URL}/me`, {
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
console.error(i18n('failedToFetchUserInfo'));
|
|
return null;
|
|
}
|
|
|
|
const userInfo = await response.json();
|
|
return userInfo;
|
|
|
|
} catch (error) {
|
|
console.error(i18n('errorFetchingUserInfo'), error);
|
|
// 可以在这里处理错误,比如清除 token 并重定向到登录页面
|
|
localStorage.removeItem('access_token');
|
|
window.location.href = 'login.html'; // 重定向到登录页面
|
|
return null;
|
|
}
|
|
} |