Initial commit

This commit is contained in:
2025-01-12 05:13:22 +00:00
commit 9553eee3e4
27 changed files with 7210 additions and 0 deletions
+94
View File
@@ -0,0 +1,94 @@
# 黑盒智能开发者平台
## 项目简介
这是一个基于Web的开发者平台,提供AI模型调用、用户管理、API密钥管理等功能。平台支持多语言(中文、英文、韩文),并提供完整的用户认证和授权系统。
## 主要功能
- 用户认证与授权
- 用户注册/登录
- 邮箱验证
- 手机号验证
- 个人资料管理
- API管理
- API密钥生成与管理
- API使用统计
- API文档浏览
- AI模型调用
- 姿态分析
- 跌倒检测
- 人脸检测
- 目标检测
- MiniCPM内容分析
- Qwen内容分析
- OCR分析
- 实时聊天
- 文字聊天
- 语音输入
- 语音合成
- 管理功能
- 用户管理
- 使用统计
- 仪表盘数据
## 项目结构
user/
├── center.js # 用户中心相关功能
├── chat.js # 聊天功能实现
├── docs.js # API文档相关功能
├── lang.js # 多语言支持
├── manage.js # 用户管理功能
├── module.js # AI模型调用模块
├── platform.html # 主平台页面
├── login.html # 登录页面
└── user/ # 后端服务
├── main.py # 主应用程序
├── .env # 环境配置
└── requirements.txt # 依赖包列表
└── picture/ # 用户头像
## 安装与部署
# 1. 安装后端依赖:
pip install -r requirements.txt
# 2. 配置环境变量:
-`.env`文件中填写必要的配置信息(数据库、Redis、SMTP等)
# 3. 启动后端服务:
uvicorn main:app --host 0.0.0.0 --port 8000
# 4. 部署web文件到Web服务器
## API基础URL
https://dev.obscura.work/v1
https://dev.obscura.work/v1_chat
## 使用说明
### main.py # 主应用程序
1. 服务器:150.158.144.159
2. PORT = 8000
3. 数据库:
1. SQL: sh-cdb-4ln6r8y0.sql.tencentcdb.com:28234/minio
2. Redis
REDIS_HOST=10.0.4.17
REDIS_PORT=13003
REDIS_DB: 0:缓存;1:用户注册;2:API密钥;3:API使用统计;
4. 登录认证:SUPABASE
## 安全说明
- 使用JWT进行用户认证
- 支持API密钥认证
- 邮箱验证
- 密码强度要求:
- 至少8个字符
- 包含大小写字母
- 包含数字
- 包含特殊字符
## 注意事项
- 确保Redis服务正常运行
- 配置正确的SMTP服务以启用邮件功能
- 确保数据库连接信息正确
- 注意保护`.env`文件中的敏感信息
+812
View File
@@ -0,0 +1,812 @@
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;
}
}
+593
View File
@@ -0,0 +1,593 @@
// chat.js
// 在文件开头添加这个函数
function i18n(key) {
return lang[selectedLang][key] || key;
}
async function showChat() {
try {
const userInfo = await fetchUserInfo();
if (!userInfo) {
alert(i18n('pleaseLogin'));
window.location.href = 'login.html'; // 重定向到登录页面
return;
}
// 检查邮箱验证状态
const emailVerified = userInfo.email_verified === 'True' || userInfo.email_verified === true;
console.log('Email Verified:', emailVerified);
if (!emailVerified) {
alert(i18n('pleaseVerifyEmail'));
showUserCenterSection('profile'); // 跳转到个人资料页面
return;
}
// 检查是否已经设置了 API 密钥
if (!window.chatAssistant.API_KEY) {
const apiKey = prompt(i18n('pleaseEnterApiKey'));
if (!apiKey) {
alert(i18n('apiKeyRequired'));
return;
}
window.chatAssistant.setApiKey(apiKey);
}
const chatContent = document.getElementById('chatContent');
const dashboardContent = document.getElementById('dashboardContent');
const usersContent = document.getElementById('usersContent');
const pageTitle = document.getElementById('pageTitle');
const featureContent = document.getElementById('featureContent');
const userCenterContent = document.getElementById('userCenterContent');
const showApiDocs = document.getElementById('showApiDocs');
const apiDocsContent = document.getElementById('apiDocsContent');
if (apiDocsContent) apiDocsContent.style.display = 'none';
if (featureContent) featureContent.style.display = 'none';
if (chatContent) chatContent.style.display = 'flex';
if (dashboardContent) dashboardContent.style.display = 'none';
if (usersContent) usersContent.style.display = 'none';
if (userCenterContent) userCenterContent.style.display = 'none';
if (pageTitle) pageTitle.innerText = i18n('chatAssistant');
if (showApiDocs) showApiDocs.style.display = 'none';
if (window.chatAssistant) {
// window.chatAssistant.setApiKey(apiKey);
window.chatAssistant.showWelcomeMessage();
}
} catch (error) {
console.error('Error in showChat:', error);
alert(i18n('errorGettingUserInfo'));
}
}
class ChatAssistant {
constructor() {
this.API_BASE_URL = 'https://dev.obscura.work/v1_chat';
this.isRecording = false;
this.API_KEY = null;
this.audioChunks = [];
this.chatHistory = [];
this.sessionId = this.generateUUID();
this.onStatusUpdate = null;
this.onMessageReceived = null;
this.currentVoice ='default';
document.addEventListener('DOMContentLoaded', () => {
this.initializeEventListeners();
});
}
setApiKey(apiKey) {
this.API_KEY = apiKey;
}
generateUUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random() * 16 | 0,
v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
setVoice(voice) {
this.currentVoice = voice;
}
initializeEventListeners() {
const micButton = document.getElementById('micButton');
const sendButton = document.getElementById('sendButton');
const textInput = document.getElementById('textInput');
const voiceSelect = document.getElementById('voiceSelect');
if (voiceSelect) {
voiceSelect.addEventListener('change', (e) => {
this.setVoice(e.target.value);
});
}
if (micButton) micButton.onclick = () => this.toggleMic();
if (sendButton) sendButton.onclick = () => this.sendTextMessage();
if (textInput) {
textInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
this.sendTextMessage();
}
});
}
}
addMessage(sender, text) {
const conversationLog = document.getElementById('conversationLog');
if (conversationLog) {
const messageDiv = document.createElement('div');
messageDiv.classList.add('message', sender === 'user' ? 'user-message' : 'assistant-message');
messageDiv.textContent = text;
conversationLog.appendChild(messageDiv);
conversationLog.scrollTop = conversationLog.scrollHeight;
return messageDiv;
} else {
console.error("conversationLog element not found");
return null;
}
}
async initializeChat() {
if (!this.API_KEY) {
throw new Error(i18n('apiKeyNotSet'));
}
try {
this.updateStatus(i18n('initializingChat'));
const response = await fetch(`${this.API_BASE_URL}/chat`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.API_KEY}`
},
body: JSON.stringify({
session_id: this.sessionId,
query: i18n('startConversation')
})
});
if (!response.ok) {
const errorData = await response.json();
console.error('Server error:', errorData);
throw new Error(`HTTP error! status: ${response.status}, message: ${JSON.stringify(errorData)}`);
}
const data = await response.json();
console.log("初始化响应:", data);
this.updateStatus(i18n('chatInitialized'));
} catch (error) {
console.error('Error initializing chat:', error);
this.updateStatus(i18n('errorInitializingChat') + error.message);
}
}
toggleMic() {
if (!this.isRecording) {
this.startRecording();
document.getElementById('micButton').classList.add('recording');
} else {
this.stopRecording();
document.getElementById('micButton').classList.remove('recording');
}
}
async startRecording() {
this.audioChunks = [];
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
this.mediaRecorder = new MediaRecorder(stream);
this.mediaRecorder.ondataavailable = event => {
this.audioChunks.push(event.data);
};
this.mediaRecorder.start();
this.isRecording = true;
this.updateStatus(i18n('recordingInProgress'));
document.getElementById('micButton').classList.add('recording');
} catch (error) {
console.error('Error accessing microphone:', error);
this.updateStatus(i18n('errorAccessingMicrophone'));
}
}
stopRecording() {
if (this.mediaRecorder && this.mediaRecorder.state !== 'inactive') {
this.mediaRecorder.stop();
this.isRecording = false;
this.updateStatus(i18n('recordingStopped'));
document.getElementById('micButton').classList.remove('recording');
this.mediaRecorder.onstop = () => {
const audioBlob = new Blob(this.audioChunks, { type: 'audio/wav' });
this.uploadRecording(audioBlob);
};
}
}
uploadRecording(audioBlob) {
const formData = new FormData();
formData.append('audio', audioBlob, 'recording.wav');
this.uploadAudio(formData);
}
async uploadAudio(formData) {
try {
const response = await fetch(`${this.API_BASE_URL}/asr`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.API_KEY}`
},
body: formData
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log('上传成功:', data);
this.pollAsrResult(data.task_id);
} catch (error) {
console.error('上传失败:', error);
this.updateStatus(i18n('uploadFailed'));
}
}
async pollAsrResult(taskId) {
let attempts = 0;
const maxAttempts = 30; // 最多轮询60秒(30 * 2秒)
const pollInterval = setInterval(async () => {
if (attempts >= maxAttempts) {
clearInterval(pollInterval);
this.updateStatus(i18n('asrProcessingTimeout'));
return;
}
attempts++;
try {
console.log(`Polling ASR result for task: ${taskId}, attempt: ${attempts}`);
const response = await fetch(`${this.API_BASE_URL}/asr_result/${taskId}`, {
headers: {
'Authorization': `Bearer ${this.API_KEY}`
}
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log('Received ASR poll response:', data);
if (data.status === 'completed') {
clearInterval(pollInterval);
console.log('ASR completed, transcription:', data.transcription);
this.displayTranscription(data.transcription);
this.processTranscription(data.transcription);
} else if (data.status === 'failed') {
clearInterval(pollInterval);
this.updateStatus(i18n('asrProcessingFailed'));
} else if (data.status === 'not_found') {
clearInterval(pollInterval);
this.updateStatus(i18n('asrTaskNotFound'));
} else {
// 其他状态,例如 'queued' 或 'processing'
this.updateStatus(i18n('asrProcessing') + `(${data.status})`);
}
} catch (error) {
console.error('轮询ASR结果时出错:', error);
clearInterval(pollInterval);
this.updateStatus(i18n('getAsrResultError'));
}
}, 2000);
}
displayTranscription(text) {
console.log('Displaying transcription:', text);
this.addMessage('user', text);
this.chatHistory.push(['user', text]);
this.updateStatus(i18n('processingTranscription'));
}
async sendTextMessage() {
const textInput = document.getElementById('textInput');
const message = textInput.value.trim();
if (message) {
console.log("Sending text message:", message);
this.addMessage('user', message);
textInput.value = '';
await this.processTranscription(message, this.currentVoice);
}
}
async processTranscription(text, voice) {
try {
this.updateStatus(i18n('generatingAIResponse'));
const chatResponse = await fetch(`${this.API_BASE_URL}/chat`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.API_KEY}`
},
body: JSON.stringify({
session_id: this.sessionId,
query: text,
voice: voice || 'default'
})
});
if (!chatResponse.ok) {
const errorData = await chatResponse.json();
console.error('Server error:', errorData);
throw new Error(`HTTP error! status: ${chatResponse.status}, message: ${JSON.stringify(errorData)}`);
}
const chatData = await chatResponse.json();
this.pollChatResult(chatData.task_id);
} catch (error) {
console.error('Error processing response:', error);
this.updateStatus(i18n('errorProcessingResponse') + error.message);
}
}
async pollChatResult(taskId) {
const pollInterval = setInterval(async () => {
try {
const response = await fetch(`${this.API_BASE_URL}/chat_result/${taskId}`, {
headers: {
'Authorization': `Bearer ${this.API_KEY}`
}
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log('收到聊天轮询响应:', data);
if (data.status === 'completed') {
clearInterval(pollInterval);
if (data.result && data.result.response) {
const aiResponse = data.result.response;
const messageElement = this.addMessage('assistant', aiResponse);
this.chatHistory.push(['assistant', aiResponse]);
const sentences = this.splitTextIntoSentences(aiResponse);
this.synthesizeAndPlaySentences([aiResponse], messageElement);
this.updateStatus(i18n('processingComplete'));
} else {
console.error('响应中的结果无效或为空:', data);
this.updateStatus(i18n('invalidChatResponse'));
}
} else if (data.status === 'failed') {
clearInterval(pollInterval);
this.updateStatus(i18n('chatProcessingFailed'));
} else {
this.updateStatus(i18n('chatProcessing') + `(${data.status})`);
}
} catch (error) {
console.error('轮询聊天结果时出错:', error);
clearInterval(pollInterval);
this.updateStatus(i18n('getChatResultError') + error.message);
}
}, 1000); // 每1秒轮询一次
}
splitTextIntoSentences(text) {
return text.split(/(?<=[!?。!?;;])\s*/);
}
async synthesizeAndPlaySentences(sentences, messageElement) {
const statusElement = document.createElement('div');
statusElement.textContent = i18n('generatingSpeech');
statusElement.style.fontStyle = 'italic';
statusElement.style.color = '#666';
statusElement.style.marginTop = '10px';
statusElement.style.fontSize = '0.9em';
messageElement.appendChild(statusElement);
for (let sentence of sentences) {
try {
const requestBody = {
text: sentence,
voice: this.currentVoice
};
console.log('Sending TTS request:', requestBody); // 打印发送的请求体
const response = await fetch(`${this.API_BASE_URL}/tts`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.API_KEY}`
},
body: JSON.stringify(requestBody)
});
console.log('TTS response status:', response.status); // 打印响应状态码
if (!response.ok) {
const errorText = await response.text();
console.error('TTS error response:', errorText); // 打印错误响应
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log('TTS response data:', data); // 打印响应数据
if (data.status === "completed") {
// 如果已经存在音频文件,直接获取并播放
await this.fetchAndPlayAudio(data.task_id, statusElement);
} else {
// 否则,开始轮询结果
await this.pollTtsResult(data.task_id, statusElement);
}
} catch (error) {
console.error(`Error in speech synthesis:`, error);
statusElement.textContent = i18n('errorProcessingResponse') + error.message;
}
}
}
async pollTtsResult(taskId, statusElement) {
const pollInterval = setInterval(async () => {
try {
const response = await fetch(`${this.API_BASE_URL}/tts_result/${taskId}`, {
headers: {
'Authorization': `Bearer ${this.API_KEY}`
}
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (data.status === 'completed') {
clearInterval(pollInterval);
await this.fetchAndPlayAudio(taskId, statusElement);
} else if (data.status === 'failed') {
clearInterval(pollInterval);
statusElement.textContent = i18n('ttsProcessingFailed');
} else {
statusElement.textContent = i18n('ttsProcessing') + `(${data.status})`;
}
} catch (error) {
console.error(i18n('ttsPollingError'), error);
clearInterval(pollInterval);
statusElement.textContent = i18n('ttsPollingError');
}
}, 1000); // 每1秒轮询一次
}
async fetchAndPlayAudio(taskId, statusElement) {
try {
const audioResponse = await fetch(`${this.API_BASE_URL}/tts_audio/${taskId}`, {
headers: {
'Authorization': `Bearer ${this.API_KEY}`
}
});
if (!audioResponse.ok) {
throw new Error(`HTTP error! status: ${audioResponse.status}`);
}
const audioBlob = await audioResponse.blob();
const audioUrl = URL.createObjectURL(audioBlob);
const audio = new Audio(audioUrl);
await audio.play();
statusElement.textContent = i18n('speechGenerationComplete');
console.log('语音生成完成,正在播放音频'); // 添加这行
} catch (error) {
console.error('获取或播放音频时出错:', error);errorFetchingOrPlayingAudio
statusElement.textContent = i18n('errorFetchingOrPlayingAudio') + error.message;
}
}
updateStatus(message) {
if (this.onStatusUpdate) {
this.onStatusUpdate(message);
}
const statusElement = document.getElementById('status');
if (statusElement) {
statusElement.textContent = message;
}
}
showWelcomeMessage() {
const welcomeMessage = i18n('welcomeMessage');
this.addMessage('assistant', welcomeMessage);
this.chatHistory.push(['assistant', welcomeMessage]);
}
}
// 创建全局实例
window.chatAssistant = new ChatAssistant();
window.initializeChat = async function() {
console.log("Generated session ID:", window.chatAssistant.sessionId);
try {
window.chatAssistant.onStatusUpdate = (status) => {
const statusElement = document.getElementById('status');
if (statusElement) statusElement.textContent = status;
};
window.chatAssistant.onMessageReceived = (sender, message) => {
console.log(`Received message from ${sender}: ${message}`);
};
await window.chatAssistant.initializeChat();
window.chatAssistant.showWelcomeMessage();
} catch (error) {
console.error("Error initializing ChatAssistant:", error);
}
};
window.activateAssistant = function() {
const dialog = document.getElementById('assistantDialog');
if (dialog) {
dialog.style.display = 'block';
setTimeout(() => {
dialog.style.opacity = '1';
dialog.style.transform = 'translate(0, 0)';
}, 10);
}
};
window.closeAssistantDialog = function() {
const dialog = document.getElementById('assistantDialog');
if (dialog) {
dialog.style.opacity = '0';
dialog.style.transform = 'translate(20px, 20px)';
setTimeout(() => dialog.style.display = 'none', 300);
}
};
window.sendMessage = async function() {
if (window.chatAssistant) {
await window.chatAssistant.sendTextMessage();
}
};
window.toggleMic = function() {
if (window.chatAssistant) {
window.chatAssistant.toggleMic();
}
};
// 获取用户信息的函数
async function fetchUserInfo() {
try {
const token = localStorage.getItem('access_token');
if (!token) {
console.error('No access token found');
return null;
}
const response = await fetch(`${BASE_URL}/me`, {
headers: {
'Authorization': `Bearer ${token}`
}
});
if (!response.ok) {
throw new Error('Failed to fetch user info');
}
const userInfo = await response.json();
return userInfo;
} catch (error) {
console.error('Error fetching user info:', error);
// 可以在这里处理错误,比如清除 token 并重定向到登录页面
localStorage.removeItem('access_token');
window.location.href = 'login.html'; // 重定向到登录页面
return null;
}
}
+96
View File
@@ -0,0 +1,96 @@
// 显示API文档
// 在文件开头添加这个函数
function i18n(key) {
return lang[selectedLang][key] || key;
}
async function showApiDocs() { // 添加 async 关键字
try {
const userInfo = await fetchUserInfo();
if (!userInfo) {
alert(i18n('pleaseLogin'));
window.location.href = 'login.html'; // 重定向到登录页面
return;
}
// 检查邮箱验证状态
const emailVerified = userInfo.email_verified === 'True' || userInfo.email_verified === true;
console.log('Email Verified:', emailVerified); // 打印邮箱验证状态
if (!emailVerified) {
alert(i18n('pleaseVerifyEmail'));
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 apiDocsContent = document.getElementById('apiDocsContent'); // 新增这一行
const pageTitle = document.getElementById('pageTitle');
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 = 'none';
if (apiDocsContent) apiDocsContent.style.display = 'block'; // 新增这一行
if (pageTitle) {
pageTitle.innerText = i18n('apiDocs');
} // 添加缺失的闭合大括号
} catch (error) {
console.error('Error in showApiDocs:', error); // 修改错误信息
alert(i18n('getUserInfoFailed') + error);
}
}
// 初始化API文档
function initApiDocs() {
const docLink = document.querySelector('a[href="#"][onclick="showApiDocs()"]');
if (docLink) {
docLink.onclick = (e) => {
e.preventDefault();
showApiDocs();
};
}
}
// 页面加载完成后初始化API文档
window.addEventListener('DOMContentLoaded', initApiDocs);
// 将函数添加到全局作用域
window.showApiDocs = showApiDocs;
// 获取用户信息的函数
async function fetchUserInfo() {
try {
const token = localStorage.getItem('access_token');
if (!token) {
console.error('No access token found');
return null;
}
const response = await fetch(`${BASE_URL}/me`, {
headers: {
'Authorization': `Bearer ${token}`
}
});
if (!response.ok) {
throw new Error(i18n('failedToFetchUserInfo'));
}
const userInfo = await response.json();
return userInfo;
} catch (error) {
console.error('Error fetching user info:', error);
// 可以在这里处理错误,比如清除 token 并重定向到登录页面
localStorage.removeItem('access_token');
window.location.href = 'login.html'; // 重定向到登录页面
return null;
}
}
+828
View File
@@ -0,0 +1,828 @@
// lang.js
const lang = {
zh: {
"experienceCenter": "体验中心",
"userManagement": "用户管理",
"userCenter": "用户中心",
"systemSettings": "系统设置",
"pageTitle": "黑盒智能开发者平台",
"dashboard": "仪表盘",
"profile": "个人资料",
"apiKey": "API Key",
"apiUsage": "模型调用",
"docs": "文档",
"logout": "退出账户",
"search": "搜索...",
"notifications": "通知",
"addUser": "添加",
"exportUserData": "导出",
"id": "ID",
"name": "姓名",
"email": "邮箱",
"role": "角色",
"actions": "操作",
"chatAssistant": "智能助手",
"inputPlaceholder": "输入文字或点击麦克风按钮开始语音输入",
"defaultVoice": "默认音色",
"girlVoice": "女孩",
"womanVoice": "年轻女性",
"manVoice": "年轻男性",
"leijun": "雷军",
"dufu": "杜甫",
"hejiong": "何炅",
"mahuateng": "马化腾",
"lidan": "李诞",
"yuhua": "余华",
"liuzhenyun": "刘震云",
"dabing": "大冰",
"luoxiang": "罗翔",
"xuzhiyuan": "许知远",
"poseAnalysis": "姿态分析",
"fallDetection": "跌倒检测",
"objectDetection": "目标检测",
"faceDetection": "人脸检测",
"compare": "面部特征提取",
"mediapipeFaceRecognition": "mediapipe面部识别",
"miniCPMContentAnalysis": "MiniCPM内容分析",
"qwenContentAnalysis": "Qwen内容分析",
"miniCPMAnalysis": "MiniCPM OCR 分析",
"qwenAnalysis": "Qwen OCR 分析",
"inputImageUrl": "输入图片URL",
"selectFile": "选择文件",
"inputApiKey": "输入API Key",
"takePicture": "拍照",
"recordVideo": "录制视频",
"stopRecording": "停止录制",
"clear": "清除",
"pleaseInputImageUrl": "请输入图片URL或上传图片",
"loading": "加载中...",
"detectionResult": "检测结果",
"settings": "设置",
"updateProfile": "更新个人资料",
"generateApiKey": "生成新的 API 密钥",
"copyApiKey": "复制 API 密钥",
"deleteApiKey": "删除 API 密钥",
"apiKeyStatus": "API 密钥状态",
"active": "激活",
"inactive": "未激活",
"usageStatistics": "使用统计",
"totalCalls": "总调用次数",
"remainingCalls": "剩余调用次数",
"apiDocs": "API 文档",
"quickStart": "快速开始",
"welcomeApi": "欢迎使用我们的API。本文档将指导您如何快速开始使用我们的服务。",
"baseUrl": "基础URL",
"authentication": "认证",
"authDescription": "所有API请求都需要使用API密钥进行认证。您可以在用户中心的API密钥页面获取您的API密钥。",
"sampleCode": "示例代码",
"pythonCode": "Python",
"replaceApiKey": "替换为您的 API 密钥",
"replaceFilePath": "替换为您的文件路径",
"replaceModelName": "替换为模型名称",
"annotatedImageSaved": "标注图片已保存为",
"taskFailed": "任务失败:",
"unknownError": "未知错误",
"timeoutMessage": "等待超时,请稍后手动检查结果",
"pleaseLogin": "请登录以使用智能助手。",
"pleaseVerifyEmail": "请先完成邮箱验证。",
"errorGettingUserInfo": "获取用户信息时出错。请重试。",
"tokenUsage": "Token 使用情况:",
"pleaseSelectFile": "请选择一个文件!",
"pleaseEnterApiKey": "请输入 API Key",
"errorUploadingFile": "上传文件时出错",
"processingFailed": "处理失败",
"errorGettingResults": "获取结果时出错",
"resultImageNotFound": "结果图片不存在或尚未生成",
"errorGettingResultImage": "获取结果图片时出错",
"cameraAccessFailed": "摄像头访问失败: ",
"cameraPermissionDenied": "无法访问摄像头,请确保已授予权限。",
"featureDemo": "功能展示",
"totalUsers": "总用户数",
"newUsersToday": "今日新增用户",
"activeUsers": "活跃用户",
"recentNewUsers": "最近新增用户",
"newUser": "新用户",
"justNow": "刚刚",
"minutesAgo": "分钟前",
"hoursAgo": "小时前",
"daysAgo": "天前",
"numberOfNewUsers": "新增用户数量",
"date": "日期",
"updateUserFailed": "更新用户失败",
"userInfoUpdateSuccess": "用户信息更新成功",
"updateUserError": "更新用户时出错:",
"updateUserFailed": "更新用户失败。请重试。",
"confirmDeleteUser": "确定要删除这个用户吗?",
"deleteUserFailed": "删除用户失败",
"userDeleteSuccess": "用户删除成功",
"deleteUserError": "删除用户时出错:",
"deleteUserFailed": "删除用户失败。请重试。",
"noUserDataToExport": "没有用户数据可导出",
"addUserFailed": "添加用户失败",
"addUserSuccess": "用户添加成功",
"addUserError": "添加用户时出错:",
"addUserFailed": "添加用户失败。请重试。",
"fetchDashboardDataFailed": "获取仪表板数据失败。请重试。",
"noRecentUsersData": "没有最近用户数据或格式无效",
"noRecentUserActivities": "没有最近的用户活动",
"activityListNotFound": "未找到活动列表元素",
"pleaseLoginToViewUserList": "请登录以查看用户列表。",
"pleaseCompleteEmailVerification": "请先完成邮箱验证。",
"logoutError": "登出时发生错误:",
"pleaseLoginToViewDashboard": "请登录以查看仪表盘。",
"enterNewUsername": "请输入新的用户名:",
"enterNewEmail": "请输入新的邮箱:",
"enterNewPhoneNumber": "请输入新的手机号码:",
"enterNewPassword": "请输入新的密码:",
"allFieldsRequired": "所有字段都是必填的",
"basicInfo": "基本资料",
"username": "用户名",
"usernameDescription": "您的用户名将显示在您的贡献或被提及的地方。",
"emailDescription": "您可以在邮箱设置中管理验证过的邮箱地址。",
"phoneNumber": "手机号",
"updateProfile": "更新资料",
"editAvatar": "编辑",
"userVerification": "用户验证",
"emailVerification": "邮箱验证:",
"verified": "已验证",
"unverified": "未验证",
"sendVerificationEmail": "发送验证邮件",
"enterVerificationCode": "输入验证码",
"submitVerificationCode": "提交验证码",
"phoneVerification": "手机验证:",
"sendPhoneVerification": "发送验证短信",
"enterPhoneVerificationCode": "输入验证码",
"submitPhoneVerificationCode": "提交验证码",
"selectAvatar": "选择头像",
"close": "关闭",
"apiKeyTable": "API密钥(点击复制)",
"createdAt": "创建时间",
"expiresAt": "过期时间",
"createNewApiKey": "创建新密钥",
"refreshList": "刷新",
"noUsageRecord": "暂无使用记录",
"tokensUsed": "已使用量",
"totalTokens": "总使用量",
"lastUsedAt": "最后使用时间",
"unused": "未使用",
"apiUsage": "API 使用情况",
"modelUsage": "模型调用情况",
"personalInfo": "个人资料",
"apiKeys": "API 密钥",
"modelCalls": "模型调用",
"userCenter": "用户中心",
"loadProfileFailed": "加载用户资料失败",
"loadProfileError": "加载用户资料时出错:",
"verificationEmailSent": "验证邮件已发送,请查收您的邮箱。",
"resend": "重新发送",
"sendVerificationEmailFailed": "发送验证邮件失败: ",
"sendVerificationEmailError": "发送验证邮件时发生错误,请稍后重试。",
"emailVerificationSuccess": "邮箱验证成功!",
"verificationFailed": "验证失败: ",
"submitVerificationCodeError": "提交验证码时发生错误,请稍后重试。",
"verificationSMSSent": "验证短信已发送,请查收您的手机。",
"sendVerificationSMSFailed": "发送验证短信失败: ",
"sendVerificationSMSError": "发送验证短信时发生错误,请稍后重试。",
"phoneVerificationSuccess": "手机验证成功!",
"submitPhoneVerificationCodeError": "提交手机验证码时发生错误,请稍后重试。",
"loadAvatarOptionsError": "加载头像选项时出错:",
"avatarUpdateSuccess": "头像更新成功",
"avatarUpdateFailed": "头像更新失败:",
"avatarUpdateError": "更新头像时发生错误,请稍后重试",
"profileUpdateSuccess": "个人资料更新成功",
"profileUpdateFailed": "更新失败: ",
"profileUpdateError": "更新失败,请重试",
"invalidApiKey": "无效的 API 密钥",
"loadApiKeysFailed": "加载API密钥失败,请稍后重试。",
"loadApiKeysError": "加载API密钥时发生错误,请稍后重试。",
"apiKeyCopied": "API密钥已复制到剪贴板",
"copyTextFailed": "无法复制文本: ",
"newApiKeyGenerated": "新的 API 密钥已生成: ",
"expirationTime": "过期时间: ",
"generateNewApiKeyFailed": "生成新的 API 密钥失败,请重试",
"generateNewApiKeyError": "生成新API密钥时出错:",
"confirmDeleteApiKey": "确定要删除这个API密钥吗?",
"deleteApiKeyFailed": "删除API密钥失败,请重试",
"deleteApiKeyError": "删除API密钥时出错:",
"cacheCleared": "缓存已清除",
"clearCacheFailed": "清除缓存失败:",
"apiKeyListRefreshed": "API密钥列表已刷新",
"loadModelCallInfoFailed": "加载模型调用信息失败",
"loadModelCallInfoError": "加载模型调用信息时发生错误,请稍后重试。",
"reload": "重新加载",
"modelName": "模型名称",
"lastCallTime": "最后调用时间",
"usage": "使用量",
"unused": "未使用",
"loadModelUsageFailed": "加载模型使用情况失败",
"loadModelUsageError": "加载模型使用情况时发生错误,请稍后重试。",
"getUserInfoFailed": "获取用户信息失败:",
"operation": "操作",
"noAccessTokenFound": "No access token found",
"failedToFetchUserInfo": "Failed to fetch user info:",
"submittingVerificationCode": "正在提交验证码:",
"delete": "删除",
"pleaseEnterApiKey": "请输入您的API密钥:",
"apiKeyRequired": "需要API密钥才能使用智能助手。",
"recordingInProgress": "正在录音...",
"recordingStopped": "录音已停止,正在处理...",
"uploadFailed": "上传失败",
"processingTranscription": "收到转录结果,正在处理...",
"generatingAIResponse": "正在生成AI回答...",
"processingComplete": "处理完成",
"errorProcessingResponse": "处理回答时出错: ",
"generatingSpeech": "语音生成中...",
"speechGenerationComplete": "语音生成完成",
"errorFetchingOrPlayingAudio": "获取或播放音频时出错: ",
"ttsProcessingFailed": "TTS处理失败",
"ttsProcessing": "TTS处理中... ({status})",
"ttsPollingError": "获取TTS结果时出错",
"speechGenerationComplete": "语音生成完成,正在播放音频",
"errorFetchingOrPlayingAudio": "获取或播放音频时出错: ",
"receivedChatPollResponse": "收到聊天轮询响应:",
"invalidChatResponse": "聊天响应格式无效",
"chatProcessingFailed": "聊天处理失败",
"chatProcessing": "聊天处理中... ({status})",
"chatPollingError": "轮询聊天结果时出错:",
"getChatResultError": "获取聊天结果时出错: ",
"uploadSuccess": "上传成功:",
"uploadFailed": "上传失败:",
"asrProcessingTimeout": "ASR处理超时",
"pollingAsrResult": "正在获取ASR结果,任务: {taskId}, 尝试次数: {attempts}",
"receivedAsrPollResponse": "收到ASR轮询响应:",
"asrCompleted": "ASR完成,转录结果:",
"asrProcessingFailed": "ASR处理失败",
"asrTaskNotFound": "ASR任务未找到",
"asrProcessing": "ASR处理中... ({status})",
"pollingAsrResultError": "轮询ASR结果时出错:",
"getAsrResultError": "获取ASR结果时出错",
"apiKeyNotSet": "API密钥未设置",
"initializingChat": "正在初始化对话...",
"serverError": "服务器错误:",
"chatInitialized": "对话已初始化,请开始输入",
"errorInitializingChat": "初始化对话时出错: ",
"startConversation": "开始对话",
"errorAccessingMicrophone": "无法访问麦克风",
"initializationResponse": "初始化响应:",
"welcomeMessage": "你好!我是你的智能语音助手小Box。",
"save":"保存",
"cancel":"取消",
"editUser":"编辑用户信息",
"addNewUser": "添加新用户",
"password": "密码",
"phoneNumber": "手机号码",
"userRole": "普通用户",
"adminRole": "管理员",
'searchPlaceholder': '搜索用户名、邮箱或角色(admin/user)...'
},
en: {
"experienceCenter": "Experience",
"userManagement": "Management",
"userCenter": "UserCenter",
"systemSettings": "Settings",
"pageTitle": "Black Box AI Developer Platform",
"dashboard": "Dashboard",
"profile": "Profile",
"apiKey": "API Key",
"apiUsage": "API Usage",
"docs": "Documentation",
"logout": "Logout",
"search": "Search...",
"notifications": "Notifications",
"addUser": "Add",
"exportUserData": "Export",
"id": "ID",
"name": "Name",
"email": "Email",
"role": "Role",
"actions": "Actions",
"chatAssistant": "Chat Assistant",
"inputPlaceholder": "Type a message or click the microphone to start voice input",
"defaultVoice": "Default Voice",
"girlVoice": "Girl",
"womanVoice": "Woman",
"manVoice": "Man",
"leijun": "Lei Jun",
"dufu": "Du Fu",
"hejiong": "He Jiong",
"mahuateng": "Ma Huateng",
"lidan": "Li Dan",
"yuhua": "Yu Hua",
"liuzhenyun": "Liu Zhenyun",
"dabing": "Da Bing",
"luoxiang": "Luo Xiang",
"xuzhiyuan": "Xu Zhiyuan",
"poseAnalysis": "Pose Analysis",
"fallDetection": "Fall Detection",
"objectDetection": "Object Detection",
"faceDetection": "Face Detection",
"compare": "Face Feature Detection",
"mediapipeFaceRecognition": "Mediapipe Face Recognition",
"miniCPMContentAnalysis": "MiniCPM Content Analysis",
"miniCPMAnalysis": "MiniCPM OCR Analysis",
"qwenAnalysis": "Qwen OCR Analysis",
"qwenContentAnalysis": "Qwen Content Analysis",
"inputImageUrl": "Input Image URL",
"selectFile": "Select File",
"inputApiKey": "Input API Key",
"takePicture": "Take Picture",
"recordVideo": "Record Video",
"stopRecording": "Stop Recording",
"clear": "Clear",
"pleaseInputImageUrl": "Please input image URL or upload an image",
"loading": "Loading...",
"detectionResult": "Detection Result",
"settings": "Settings",
"updateProfile": "Update Profile",
"generateApiKey": "Generate New API Key",
"copyApiKey": "Copy API Key",
"deleteApiKey": "Delete API Key",
"apiKeyStatus": "API Key Status",
"active": "Active",
"inactive": "Inactive",
"usageStatistics": "Usage Statistics",
"totalCalls": "Total Calls",
"remainingCalls": "Remaining Calls",
"apiDocs": "API Documentation",
"quickStart": "Quick Start",
"welcomeApi": "Welcome to our API. This document will guide you on how to quickly start using our services.",
"baseUrl": "Base URL",
"authentication": "Authentication",
"authDescription": "All API requests require authentication using an API key. You can obtain your API key from the API key page in the user center.",
"sampleCode": "Sample Code",
"pythonCode": "Python",
"replaceApiKey": "Replace with your API key",
"replaceFilePath": "Replace with your file path",
"replaceModelName": "Replace with model name",
"annotatedImageSaved": "Annotated image has been saved as",
"taskFailed": "Task failed:",
"unknownError": "Unknown error",
"timeoutMessage": "Timeout waiting, please check the result manually later",
"pleaseLogin": "Please login to use the intelligent assistant.",
"pleaseVerifyEmail": "Please complete email verification first.",
"errorGettingUserInfo": "Error getting user information. Please try again.",
"tokenUsage": "Token usage:",
"pleaseSelectFile": "Please select a file!",
"pleaseEnterApiKey": "Please enter API Key!",
"errorUploadingFile": "Error uploading file",
"processingFailed": "Processing failed",
"errorGettingResults": "Error getting results",
"resultImageNotFound": "Result image not found or not yet generated",
"errorGettingResultImage": "Error getting result image",
"cameraAccessFailed": "Camera access failed: ",
"cameraPermissionDenied": "Unable to access camera. Please ensure permission is granted.",
"featureDemo": "Feature Demo",
"totalUsers": "Total Users",
"newUsersToday": "New Users Today",
"activeUsers": "Active Users",
"recentNewUsers": "Recent New Users",
"newUser": "New user",
"justNow": "Just now",
"minutesAgo": "minutes ago",
"hoursAgo": "hours ago",
"daysAgo": "days ago",
"numberOfNewUsers": "Number of New Users",
"date": "Date",
"updateUserFailed": "Failed to update user",
"userInfoUpdateSuccess": "User information updated successfully",
"updateUserError": "Error updating user:",
"updateUserFailed": "Failed to update user. Please try again.",
"confirmDeleteUser": "Are you sure you want to delete this user?",
"deleteUserFailed": "Failed to delete user",
"userDeleteSuccess": "User deleted successfully",
"deleteUserError": "Error deleting user:",
"deleteUserFailed": "Failed to delete user. Please try again.",
"noUserDataToExport": "No user data to export",
"addUserFailed": "Failed to add user",
"addUserSuccess": "User added successfully",
"addUserError": "Error adding user:",
"addUserFailed": "Failed to add user. Please try again.",
"fetchDashboardDataFailed": "Failed to fetch dashboard data. Please try again.",
"noRecentUsersData": "No recent users data or invalid format",
"noRecentUserActivities": "No recent user activities available",
"activityListNotFound": "Activity list element not found",
"pleaseLoginToViewUserList": "Please login to view the user list.",
"pleaseCompleteEmailVerification": "Please complete email verification first.",
"logoutError": "Error occurred during logout:",
"pleaseLoginToViewDashboard": "Please login to view the dashboard.",
"enterNewUsername": "Please enter new username:",
"enterNewEmail": "Please enter new email:",
"enterNewPhoneNumber": "Please enter new phone number:",
"enterNewPassword": "Please enter new password:",
"allFieldsRequired": "All fields are required",
"basicInfo": "Basic Information",
"username": "Username",
"usernameDescription": "Your username will be displayed where you contribute or are mentioned.",
"emailDescription": "You can manage verified email addresses in email settings.",
"phoneNumber": "Phone Number",
"updateProfile": "Update Profile",
"editAvatar": "Edit",
"userVerification": "User Verification",
"emailVerification": "Email Verification:",
"verified": "Verified",
"unverified": "Unverified",
"sendVerificationEmail": "Send Verification Email",
"enterVerificationCode": "Enter Verification Code",
"submitVerificationCode": "Submit Verification Code",
"phoneVerification": "Phone Verification:",
"sendPhoneVerification": "Send Verification SMS",
"enterPhoneVerificationCode": "Enter Verification Code",
"submitPhoneVerificationCode": "Submit Verification Code",
"selectAvatar": "Select Avatar",
"close": "Close",
"apiKeyTable": "API Key (Click to Copy)",
"createdAt": "Created At",
"expiresAt": "Expires At",
"createNewApiKey": "Create New Key",
"refreshList": "Refresh",
"noUsageRecord": "No Usage Record",
"tokensUsed": "Tokens Used",
"totalTokens": "Total Tokens",
"lastUsedAt": "Last Used At",
"unused": "Unused",
"apiUsage": "API Usage",
"modelUsage": "Model Usage",
"personalInfo": "Personal Information",
"apiKeys": "API Keys",
"modelCalls": "Model Calls",
"userCenter": "User Center",
"loadProfileFailed": "Failed to load user profile",
"loadProfileError": "Error loading user profile: ",
"verificationEmailSent": "Verification email has been sent. Please check your inbox.",
"resend": "Resend",
"sendVerificationEmailFailed": "Failed to send verification email: ",
"sendVerificationEmailError": "An error occurred while sending the verification email. Please try again later.",
"emailVerificationSuccess": "Email verification successful!",
"verificationFailed": "Verification failed: ",
"submitVerificationCodeError": "An error occurred while submitting the verification code. Please try again later.",
"verificationSMSSent": "Verification SMS has been sent. Please check your phone.",
"sendVerificationSMSFailed": "Failed to send verification SMS: ",
"sendVerificationSMSError": "An error occurred while sending the verification SMS. Please try again later.",
"phoneVerificationSuccess": "Phone verification successful!",
"submitPhoneVerificationCodeError": "An error occurred while submitting the phone verification code. Please try again later.",
"loadAvatarOptionsError": "Error loading avatar options: ",
"avatarUpdateSuccess": "Avatar updated successfully",
"avatarUpdateFailed": "Avatar update failed: ",
"avatarUpdateError": "An error occurred while updating the avatar. Please try again later",
"profileUpdateSuccess": "Profile updated successfully",
"profileUpdateFailed": "Update failed: ",
"profileUpdateError": "Update failed. Please try again",
"invalidApiKey": "Invalid API key",
"loadApiKeysFailed": "Failed to load API keys. Please try again later.",
"loadApiKeysError": "An error occurred while loading API keys. Please try again later.",
"apiKeyCopied": "API key copied to clipboard",
"copyTextFailed": "Unable to copy text: ",
"newApiKeyGenerated": "New API key generated: ",
"expirationTime": "Expiration time: ",
"generateNewApiKeyFailed": "Failed to generate new API key. Please try again",
"generateNewApiKeyError": "Failed to generate new API key. Please try again",
"confirmDeleteApiKey": "Are you sure you want to delete this API key?",
"deleteApiKeyFailed": "Failed to delete API key. Please try again",
"deleteApiKeyError": "Failed to delete API key. Please try again",
"cacheCleared": "Cache cleared",
"clearCacheFailed": "Failed to clear cache: ",
"apiKeyListRefreshed": "API key list refreshed",
"loadModelCallInfoFailed": "Failed to load model call information",
"loadModelCallInfoError": "An error occurred while loading model call information. Please try again later.",
"reload": "Reload",
"modelName": "Model Name",
"lastCallTime": "Last Call Time",
"usage": "Usage",
"unused": "Unused",
"loadModelUsageFailed": "Failed to load model usage information",
"loadModelUsageError": "An error occurred while loading model usage information. Please try again later.",
"getUserInfoFailed": "Failed to get user information: ",
"operation": "Operation",
"noAccessTokenFound": "No access token found",
"failedToFetchUserInfo": "Failed to fetch user info:",
"submittingVerificationCode": "Submitting verification code:",
"delete": "Delete",
"pleaseEnterApiKey": "Please enter your API key:",
"apiKeyRequired": "API key is required to use the intelligent assistant.",
"recordingInProgress": "Recording in progress...",
"recordingStopped": "Recording stopped, processing...",
"uploadFailed": "Upload failed",
"processingTranscription": "Received transcription result, processing...",
"generatingAIResponse": "Generating AI response...",
"processingComplete": "Processing complete",
"errorProcessingResponse": "Error processing response: ",
"generatingSpeech": "Generating speech...",
"speechGenerationComplete": "Speech generation complete",
"errorFetchingOrPlayingAudio": "Error fetching or playing audio: ",
"ttsProcessingFailed": "TTS processing failed",
"ttsProcessing": "TTS processing... ({status})",
"ttsPollingError": "Error getting TTS results",
"speechGenerationComplete": "Speech generation complete, playing audio",
"errorFetchingOrPlayingAudio": "Error fetching or playing audio: ",
"receivedChatPollResponse": "Received chat poll response:",
"invalidChatResponse": "Invalid chat response format",
"chatProcessingFailed": "Chat processing failed",
"chatProcessing": "Chat processing... ({status})",
"chatPollingError": "Error polling chat result:",
"getChatResultError": "Error getting chat result: ",
"uploadSuccess": "Upload successful:",
"uploadFailed": "Upload failed:",
"asrProcessingTimeout": "ASR processing timeout",
"pollingAsrResult": "Polling ASR result for task: {taskId}, attempt: {attempts}",
"receivedAsrPollResponse": "Received ASR poll response:",
"asrCompleted": "ASR completed, transcription:",
"asrProcessingFailed": "ASR processing failed",
"asrTaskNotFound": "ASR task not found",
"asrProcessing": "ASR processing... ({status})",
"pollingAsrResultError": "Error polling ASR result:",
"getAsrResultError": "Error getting ASR result",
"apiKeyNotSet": "API key not set",
"initializingChat": "Initializing chat...",
"serverError": "Server error:",
"chatInitialized": "Chat initialized, please start input",
"errorInitializingChat": "Error initializing chat: ",
"startConversation": "Start conversation",
"errorAccessingMicrophone": "Unable to access microphone",
"initializationResponse": "Initialization response:",
"welcomeMessage": "Hello! I'm your intelligent voice assistant, Little Box.",
"save":"save",
"cancel":"cancel",
"editUser":"Edit User Data",
"addNewUser": "Add New User",
"password": "Password",
"phoneNumber": "Phone Number",
"userRole": "User",
"adminRole": "Admin",
'searchPlaceholder': 'Search by name, email or role(admin/user)...'
},
ko: {
"experienceCenter": "체험 센터",
"userManagement": "사용자 관리",
"userCenter": "사용자 센터",
"systemSettings": "시스템 설정",
"pageTitle": "블랙박스 AI 개발자 플랫폼",
"dashboard": "대시보드",
"profile": "프로필",
"apiKey": "API Key",
"apiUsage": "API 사용량",
"docs": "문서",
"logout": "로그아웃",
"search": "검색...",
"notifications": "알림",
"addUser": "추가",
"exportUserData": "내보내기",
"id": "ID",
"name": "이름",
"email": "이메일",
"role": "역할",
"actions": "작업",
"chatAssistant": "채팅 도우미",
"inputPlaceholder": "메시지를 입력하거나 마이크를 클릭하여 음성 입력 시작",
"defaultVoice": "기본 음성",
"girlVoice": "소녀",
"womanVoice": "여성",
"manVoice": "남성",
"leijun": "레이쥔",
"dufu": "두보",
"hejiong": "허중",
"mahuateng": "마화텅",
"lidan": "리단",
"yuhua": "위화",
"liuzhenyun": "류전윈",
"dabing": "다빙",
"luoxiang": "뤄샹",
"xuzhiyuan": "쉬즈위안",
"poseAnalysis": "자세 분석",
"fallDetection": "낙상 감지",
"objectDetection": "객체 감지",
"faceDetection": "얼굴 감지",
"compare": "얼굴 특징 감지",
"mediapipeFaceRecognition": "Mediapipe 얼굴 인식",
"miniCPMContentAnalysis": "MiniCPM 콘텐츠 분석",
"qwenContentAnalysis": "Qwen 콘텐츠 분석",
"miniCPMAnalysis": "MiniCPM OCR 분석",
"qwenAnalysis": "Qwen OCR 분석",
"inputImageUrl": "이미지 URL 입력",
"selectFile": "파일 선택",
"inputApiKey": "API 키 입력",
"takePicture": "사진 찍기",
"recordVideo": "비디오 녹화",
"stopRecording": "녹화 중지",
"clear": "지우기",
"pleaseInputImageUrl": "이미지 URL을 입력하거나 이미지를 업로드하세요",
"loading": "로딩 중...",
"detectionResult": "감지 결과",
"settings": "설정",
"updateProfile": "프로필 업데이트",
"generateApiKey": "새 API 키 생성",
"copyApiKey": "API 키 복사",
"deleteApiKey": "API 키 삭제",
"apiKeyStatus": "API 키 상태",
"active": "활성",
"inactive": "비활성",
"usageStatistics": "사용 통계",
"totalCalls": "총 호출 수",
"remainingCalls": "남은 호출 수",
"apiDocs": "API 문서",
"quickStart": "빠른 시작",
"welcomeApi": "API를 사용해 주셔서 감사합니다. 이 문서는 서비스 사용을 빠르게 시작하는 방법을 안내합니다.",
"baseUrl": "기본 URL",
"authentication": "인증",
"authDescription": "모든 API 요청은 API 키를 사용한 인증이 필요합니다. 사용자 센터의 API 키 페이지에서 API 키를 얻을 수 있습니다.",
"sampleCode": "샘플 코드",
"pythonCode": "Python",
"replaceApiKey": "API 키로 교체하세요",
"replaceFilePath": "파일 경로로 교체하세요",
"replaceModelName": "모델 이름으로 교체하세요",
"annotatedImageSaved": "주석이 달린 이미지가 다음과 같이 저장되었습니다",
"taskFailed": "작업 실패:",
"unknownError": "알 수 없는 오류",
"timeoutMessage": "대기 시간 초과, 나중에 수동으로 결과를 확인해 주세요",
"pleaseLogin": "지능형 어시스턴트를 사용하려면 로그인하세요.",
"pleaseVerifyEmail": "먼저 이메일 인증을 완료해주세요.",
"errorGettingUserInfo": "사용자 정보를 가져오는 중 오류가 발생했습니다. 다시 시도해주세요.",
"tokenUsage": "토큰 사용량:",
"pleaseSelectFile": "파일을 선택해주세요!",
"pleaseEnterApiKey": "API 키를 입력해주세요!",
"errorUploadingFile": "파일 업로드 중 오류 발생",
"processingFailed": "처리 실패",
"errorGettingResults": "결과를 가져오는 중 오류 발생",
"resultImageNotFound": "결과 이미지를 찾을 수 없거나 아직 생성되지 않았습니다",
"errorGettingResultImage": "결과 이미지를 가져오는 중 오류 발생",
"cameraAccessFailed": "카메라 접근 실패: ",
"cameraPermissionDenied": "카메라에 접근할 수 없습니다. 권한이 부여되었는지 확인해주세요.",
"featureDemo": "기능 데모",
"totalUsers": "총 사용자 수",
"newUsersToday": "오늘의 신규 사용자",
"activeUsers": "활성 사용자",
"recentNewUsers": "최근 신규 사용자",
"newUser": "신규 사용자",
"justNow": "방금",
"minutesAgo": "분 전",
"hoursAgo": "시간 전",
"daysAgo": "일 전",
"numberOfNewUsers": "신규 사용자 수",
"date": "날짜",
"updateUserFailed": "사용자 업데이트 실패",
"userInfoUpdateSuccess": "사용자 정보가 성공적으로 업데이트되었습니다",
"updateUserError": "사용자 업데이트 중 오류 발생:",
"updateUserFailed": "사용자 업데이트에 실패했습니다. 다시 시도해주세요.",
"confirmDeleteUser": "이 사용자를 삭제하시겠습니까?",
"deleteUserFailed": "사용자 삭제 실패",
"userDeleteSuccess": "사용자가 성공적으로 삭제되었습니다",
"deleteUserError": "사용자 삭제 중 오류 발생:",
"deleteUserFailed": "사용자 삭제에 실패했습니다. 다시 시도해주세요.",
"noUserDataToExport": "내보낼 사용자 데이터가 없습니다",
"addUserFailed": "사용자 추가 실패",
"addUserSuccess": "사용자가 성공적으로 추가되었습니다",
"addUserError": "사용자 추가 중 오류 발생:",
"addUserFailed": "사용자 추가에 실패했습니다. 다시 시도해주세요.",
"fetchDashboardDataFailed": "대시보드 데이터 가져오기 실패. 다시 시도해주세요.",
"noRecentUsersData": "최근 사용자 데이터가 없거나 형식이 잘못되었습니다",
"noRecentUserActivities": "최근 사용자 활동이 없습니다",
"activityListNotFound": "활동 목록 요소를 찾을 수 없습니다",
"pleaseLoginToViewUserList": "사용자 목록을 보려면 로그인하세요.",
"pleaseCompleteEmailVerification": "먼저 이메일 인증을 완료해주세요.",
"logoutError": "로그아웃 중 오류 발생:",
"pleaseLoginToViewDashboard": "대시보드를 보려면 로그인하세요.",
"enterNewUsername": "새 사용자 이름을 입력하세요:",
"enterNewEmail": "새 이메일을 입력하세요:",
"enterNewPhoneNumber": "새 전화번호를 입력하세요:",
"enterNewPassword": "새 비밀번호를 입력하세요:",
"allFieldsRequired": "모든 필드를 입력해야 합니다",
"basicInfo": "기본 정보",
"username": "사용자 이름",
"usernameDescription": "사용자 이름은 귀하의 기여나 언급된 곳에 표시됩니다.",
"emailDescription": "이메일 설정에서 인증된 이메일 주소를 관리할 수 있습니다.",
"phoneNumber": "전화번호",
"editAvatar": "편집",
"userVerification": "사용자 인증",
"emailVerification": "이메일 인증:",
"verified": "인증됨",
"unverified": "미인증",
"sendVerificationEmail": "인증 이메일 보내기",
"enterVerificationCode": "인증 코드 입력",
"submitVerificationCode": "인증 코드 제출",
"phoneVerification": "전화번호 인증:",
"sendPhoneVerification": "인증 SMS 보내기",
"enterPhoneVerificationCode": "인증 코드 입력",
"submitPhoneVerificationCode": "인증 코드 제출",
"selectAvatar": "아바타 선택",
"close": "닫기",
"apiKeyTable": "API Key (클릭하여 복사)",
"createdAt": "생성 시간",
"expiresAt": "만료 시간",
"createNewApiKey": "새 키 생성",
"refreshList": "새로고침",
"noUsageRecord": "새 키 만들기",
"tokensUsed": "사용된 토큰",
"totalTokens": "총 토큰",
"lastUsedAt": "마지막 사용 시간",
"unused": "미사용",
"apiUsage": "API 사용 현황",
"modelUsage": "모델 사용 현황",
"personalInfo": "개인 정보",
"apiKeys": "API Keys",
"modelCalls": "모델 호출",
"userCenter": "사용자 센터",
"loadProfileFailed": "사용자 프로필 드 실패",
"loadProfileError": "사용자 프로필 로드 중 오류 발생: ",
"verificationEmailSent": "인증 이메일이 발송되었습니다. 받은 편지함을 확인해 주세요.",
"resend": "재전송",
"sendVerificationEmailFailed": "인증 이메일 발송 실패: ",
"sendVerificationEmailError": "인증 이메일 발송 중 오류가 발생했습니다. 나중에 다시 시도해 주세요.",
"emailVerificationSuccess": "이메일 인증 성공!",
"verificationFailed": "인증 실패: ",
"submitVerificationCodeError": "인증 코드 제출 중 오류가 발생했습니다. 나중에 다시 시도해 주세요.",
"verificationSMSSent": "인증 SMS가 발송되었습니다. 휴대폰을 확인해 주세요.",
"sendVerificationSMSFailed": "인증 SMS 발송 실패: ",
"sendVerificationSMSError": "인증 SMS 발송 중 오류가 발생했습니다. 나중에 다시 시도해 주세요.",
"phoneVerificationSuccess": "전화번호 인증 성공!",
"submitPhoneVerificationCodeError": "전화번호 인증 코드 제출 중 오류가 발생했습니다. 나중에 다시 시도해 주세요.",
"loadAvatarOptionsError": "아바타 옵션 로드 중 오류 발생: ",
"avatarUpdateSuccess": "아바타 업데이트 성공",
"avatarUpdateFailed": "아바타 업데이트 실패: ",
"avatarUpdateError": "아바타 업데이트 중 오류가 발생했습니다. 나중에 다시 시도해 주세요",
"profileUpdateSuccess": "프로필 업데이트 성공",
"profileUpdateFailed": "업데이트 실패: ",
"profileUpdateError": "업데이트 실패. 다시 시도해 주세요",
"invalidApiKey": "유효하지 않은 API 키",
"loadApiKeysFailed": "API 키 로드 실패. 나중에 다시 시도해 주세요.",
"loadApiKeysError": "API 키 로드 중 오류가 발생했습니다. 나중에 다시 시도해 주세요.",
"apiKeyCopied": "API 키가 클립보드에 복사되었습니다",
"copyTextFailed": "텍스트 복사 실패: ",
"newApiKeyGenerated": "새 API 키 생성: ",
"expirationTime": "만료 시간: ",
"generateNewApiKeyFailed": "새 API 키 생성 실패. 다시 시도해 주세요",
"generateNewApiKeyError": "새 API 키 생성 실패. 다시 시도해 주세요",
"confirmDeleteApiKey": "이 API 키를 삭제하시겠습니까?",
"deleteApiKeyFailed": "API 키 삭제 실패. 다시 시도해 주세요",
"deleteApiKeyError": "API 키 삭제 실패. 다시 시도해 주세요",
"cacheCleared": "캐시 삭제 완료",
"clearCacheFailed": "캐시 삭제 실패: ",
"apiKeyListRefreshed": "API 키 목록 새로고침 완료",
"loadModelCallInfoFailed": "모델 호출 정보 로드 실패",
"loadModelCallInfoError": "모델 호출 정보 로드 중 오류가 발생했습니다. 나중에 다시 시도해 주세요.",
"reload": "새로고침",
"modelName": "모델 이름",
"lastCallTime": "마지막 호출 시간",
"usage": "사용량",
"unused": "미사용",
"loadModelUsageFailed": "모델 사용 정보 로드 실패",
"loadModelUsageError": "모델 사용 정보 로드 중 오류가 발생했습니다. 나중에 다시 시도해 주세요.",
"getUserInfoFailed": "사용자 정보 가져오기 실패: ",
"operation": "작업",
"noAccessTokenFound": "액세스 토큰을 찾을 수 없습니다",
"failedToFetchUserInfo": "사용자 정보를 가져오는데 실패했습니다:",
"submittingVerificationCode": "인증 코드 제출 중:",
"delete":"삭제",
"pleaseEnterApiKey": "API 키를 입력해주세요:",
"apiKeyRequired": "지능형 어시스턴트를 사용하려면 API 키가 필요합니다.",
"recordingInProgress": "녹음 중...",
"recordingStopped": "녹음이 중지되었습니다. 처리 중...",
"uploadFailed": "업로드 실패",
"processingTranscription": "전사 결과를 받았습니다. 처리 중...",
"generatingAIResponse": "AI 응답 생성 중...",
"processingComplete": "처리 완료",
"errorProcessingResponse": "응답 처리 중 오류 발생: ",
"generatingSpeech": "음성 생성 중...",
"speechGenerationComplete": "음성 생성 완료",
"errorFetchingOrPlayingAudio": "오디오 가져오기 또는 재생 중 오류 발생: ",
"ttsProcessingFailed": "TTS 처리 실패",
"ttsProcessing": "TTS 처리 중... ({status})",
"ttsPollingError": "TTS 결과 가져오기 오류",
"speechGenerationComplete": "음성 생성 완료, 오디오 재생 중",
"errorFetchingOrPlayingAudio": "오디오 가져오기 또는 재생 중 오류 발생: ",
"receivedChatPollResponse": "채팅 폴링 응답 수신:",
"invalidChatResponse": "채팅 응답 형식이 잘못되었습니다",
"chatProcessingFailed": "채팅 처리 실패",
"chatProcessing": "채팅 처리 중... ({status})",
"chatPollingError": "채팅 결과 폴링 중 오류 발생:",
"getChatResultError": "채팅 결과를 가져오는 중 오류 발생: ",
"uploadSuccess": "업로드 성공:",
"uploadFailed": "업로드 실패:",
"asrProcessingTimeout": "ASR 처리 시간 초과",
"pollingAsrResult": "ASR 결과 폴링 중, 작업: {taskId}, 시도 횟수: {attempts}",
"receivedAsrPollResponse": "ASR 폴링 응답 수신:",
"asrCompleted": "ASR 완료, 전사 결과:",
"asrProcessingFailed": "ASR 처리 실패",
"asrTaskNotFound": "ASR 작업을 찾을 수 없음",
"asrProcessing": "ASR 처리 중... ({status})",
"pollingAsrResultError": "ASR 결과 폴링 중 오류 발생:",
"getAsrResultError": "ASR 결과 가져오기 중 오류 발생",
"apiKeyNotSet": "API 키가 설정되지 않았습니다",
"initializingChat": "대화를 초기화하는 중...",
"serverError": "서버 오류:",
"chatInitialized": "대화가 초기화되었습니다. 입력을 시작하세요",
"errorInitializingChat": "대화 초기화 중 오류 발생: ",
"startConversation": "대화 시작",
"errorAccessingMicrophone": "마이크에 접근할 수 없습니다",
"initializationResponse": "초기화 응답:",
"welcomeMessage": "안녕하세요! 저는 당신의 지능형 음성 비서 리틀 박스입니다.",
"save":"저장",
"cancel":"취소",
"editUser":"사용자 데이터 편집",
"addNewUser": "새 사용자 추가",
"password": "비밀번호",
"phoneNumber": "전화번호",
"userRole": "일반 사용자",
"adminRole": "관리자",
'searchPlaceholder': '이름, 이메일 또는 역할(admin/user)로 검색...'
}
};
+433
View File
@@ -0,0 +1,433 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>黑盒智能开发者平台</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css" integrity="sha512-iBBXm8fW90+nuLcSKlbmrPcLa0OT92xO1BIsZ+ywDWZCvqsWgccV3gFoRBv0z+8dLJgyAHIhR35VZc2oM/gI1w==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: 'Arial', sans-serif;
}
body {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 20px;
}
.header {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20px;
}
.container {
background-color: rgba(255, 255, 255, 0.9);
border-radius: 15px;
box-shadow: 0 14px 28px rgba(0,0,0,0.25), 0 10px 10px rgba(0,0,0,0.22);
position: relative;
overflow: hidden;
width: 80%;
max-width: 1200px; /* Set a maximum width */
min-height: 900px; /* Use a fixed minimum height */
margin: 20px auto; /* Add some margin */
}
.form-container {
position: absolute;
top: 0;
height: 100%;
transition: all 0.6s ease-in-out;
}
.sign-in-container {
left: 0;
width: 50%;
z-index: 2;
}
.sign-up-container {
left: 0;
width: 50%;
opacity: 0;
z-index: 1;
}
.container.right-panel-active .sign-in-container {
transform: translateX(100%);
}
.container.right-panel-active .sign-up-container {
transform: translateX(100%);
opacity: 1;
z-index: 5;
}
form {
background-color: #FFFFFF;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
padding: 0 50px;
height: 100%;
text-align: center;
}
h1 {
font-weight: bold;
margin-bottom: 30px;
font-size: 3.5em;
}
p {
font-size: 1.2em;
margin-bottom: 30px; /* Increase bottom margin */
}
input {
background-color: #eee;
border: none;
padding: 12px 15px;
margin: 15px 0;
width: 100%;
border-radius: 10px;
}
button {
border-radius: 50px;
border: 1px solid #667eea;
background-color: #667eea;
color: #FFFFFF;
font-size: 20px;
font-weight: bold;
padding: 12px 45px;
letter-spacing: 3px;
text-transform: uppercase;
transition: transform 80ms ease-in;
margin-bottom: 50px;
}
button:active {
transform: scale(0.95);
}
button:focus {
outline: none;
}
button.ghost {
background-color: transparent;
border-color: #FFFFFF;
}
.social-container {
margin: 20px 0;
}
.social-container a {
border: 1px solid #DDDDDD;
border-radius: 50%;
display: inline-flex;
justify-content: center;
align-items: center;
margin: 0 5px;
height: 40px;
width: 40px;
}
.overlay-container {
position: absolute;
top: 0;
left: 50%;
width: 50%;
height: 100%;
overflow: hidden;
transition: transform 0.6s ease-in-out;
z-index: 100;
}
.container.right-panel-active .overlay-container{
transform: translateX(-100%);
}
.overlay {
background: #667eea;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background-repeat: no-repeat;
background-size: cover;
background-position: 0 0;
color: #FFFFFF;
position: relative;
left: -100%;
height: 100%;
width: 200%;
transform: translateX(0);
transition: transform 0.6s ease-in-out;
}
.container.right-panel-active .overlay {
transform: translateX(50%);
}
.overlay-panel {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
padding: 0 40px;
text-align: center;
top: 0;
height: 100%;
width: 50%;
transform: translateX(0);
transition: transform 0.6s ease-in-out;
}
.overlay-left {
transform: translateX(-20%);
}
.container.right-panel-active .overlay-left {
transform: translateX(0);
}
.overlay-right {
right: 0;
transform: translateX(0);
}
.container.right-panel-active .overlay-right {
transform: translateX(20%);
}
@media (max-width: 768px) {
.container {
width: 90%;
min-height: 90vh;
}
form {
padding: 0 20px;
}
.overlay-panel {
padding: 0 20px;
}
.title {
font-size: 2em;
}
.logo {
font-size: 2.5em;
}
}
</style>
</head>
<body>
<div class="container" id="container">
<div class="form-container sign-up-container">
<form id="signupForm">
<h1>Create Account</h1>
<!-- <div class="social-container">
<a href="#" class="social"><i class="fab fa-google"></i></a>
<a href="#" class="social"><i class="fab fa-github"></i></a>
</div> -->
<span>use your email for registration</span>
<input type="text" placeholder="Username" id="signupUsername" required />
<input type="password" placeholder="Password" id="signupPassword" required />
<div class="password-rules">
Password must be at least 8 characters long and include uppercase, lowercase, digit, and special character (!@#$%^&*?:).
</div>
<input type="email" placeholder="Email" id="signupEmail" required />
<input type="tel" placeholder="Phone" id="signupPhone" required />
<button type="submit">REGISTER</button>
</form>
</div>
<div class="form-container sign-in-container">
<form id="signinForm">
<h1>Sign in</h1>
<!-- <div class="social-container">
<a href="#" class="social"><i class="fab fa-google"></i></a>
<a href="#" class="social"><i class="fab fa-github"></i></a>
</div> -->
<span>use your account</span>
<input type="text" placeholder="Username" id="signinUsername" required />
<input type="password" placeholder="Password" id="signinPassword" required />
<a href="#">Forgot your password?</a>
<button type="submit">Sign In</button>
</form>
</div>
<div class="overlay-container">
<div class="overlay">
<div class="overlay-panel overlay-left">
<h1>Welcome Back!</h1>
<p>To keep connected with us please login with your personal info</p>
<button class="ghost" id="signIn">Sign In</button>
</div>
<div class="overlay-panel overlay-right">
<h1>Hello, Friend!</h1>
<p>Start your journey with us</p>
<button class="ghost" id="signUp">Sign Up</button>
</div>
</div>
</div>
</div>
<script>
const signUpButton = document.getElementById('signUp');
const signInButton = document.getElementById('signIn');
const container = document.getElementById('container');
signUpButton.addEventListener('click', () => {
container.classList.add("right-panel-active");
});
signInButton.addEventListener('click', () => {
container.classList.remove("right-panel-active");
});
const API_URL = 'https://user.obscura.work/user';
// 页面加载时检查登录状态
document.addEventListener('DOMContentLoaded', checkLoginStatus);
function checkLoginStatus() {
const token = localStorage.getItem('access_token');
if (token) {
validateToken(token);
} else {
showLoginForm();
}
}
async function validateToken(token) {
try {
const response = await fetch(`${API_URL}/me`, {
headers: {
'Authorization': `Bearer ${token}`,
},
});
if (response.ok) {
const data = await response.json();
console.log('用户信息:', data);
redirectToDashboard();
} else {
throw new Error('无效的token');
}
} catch (error) {
console.error('错误:', error);
logout();
}
}
function showLoginForm() {
container.classList.remove("right-panel-active");
}
function redirectToDashboard(token) {
window.location.href = `https://user.obscura.work/platform.html`;
}
async function logout() {
const token = localStorage.getItem('access_token');
if (token) {
try {
await fetch(`${API_URL}/logout`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
},
});
} catch (error) {
console.error('登出时发生错误:', error);
}
}
localStorage.removeItem('access_token');
showLoginForm();
}
// 注册
document.getElementById('signupForm').addEventListener('submit', async (e) => {
e.preventDefault();
const username = document.getElementById('signupUsername').value;
const password = document.getElementById('signupPassword').value;
const email = document.getElementById('signupEmail').value;
const phoneNumber = document.getElementById('signupPhone').value;
try {
const response = await fetch(`${API_URL}/register`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: username,
email: email,
phone_number: phoneNumber,
password: password
}),
});
const data = await response.json();
if (response.ok) {
alert(data.message);
showLoginForm();
} else {
let errorMessage = '注册失败: ';
if (typeof data.detail === 'object') {
for (let key in data.detail) {
errorMessage += `${key}: ${data.detail[key].join(', ')}\n`;
}
} else {
errorMessage += data.detail || '发生未知错误';
}
alert(errorMessage);
}
} catch (error) {
console.error('错误:', error);
alert('发生错误,请重试。');
}
});
// 登录
document.getElementById('signinForm').addEventListener('submit', async (e) => {
e.preventDefault();
const username = document.getElementById('signinUsername').value;
const password = document.getElementById('signinPassword').value;
try {
const response = await fetch(`${API_URL}/token`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `username=${encodeURIComponent(username)}&password=${encodeURIComponent(password)}`,
});
const data = await response.json();
if (response.ok) {
localStorage.setItem('access_token', data.access_token);
alert('登录成功');
redirectToDashboard(data.access_token);
} else {
alert(data.detail || '登录失败');
}
} catch (error) {
console.error('错误:', error);
alert('发生错误,请重试。');
}
});
// 处理需要认证的API请求
async function makeAuthenticatedRequest(url, options = {}) {
const token = localStorage.getItem('access_token');
if (!token) {
throw new Error('未找到token');
}
const headers = new Headers(options.headers || {});
headers.append('Authorization', `Bearer ${token}`);
const response = await fetch(url, { ...options, headers });
if (response.status === 401) {
logout();
throw new Error('未授权');
}
return response;
}
</script>
</body>
</html>
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

+870
View File
@@ -0,0 +1,870 @@
document.addEventListener('DOMContentLoaded', () => {
const themeToggle = document.getElementById('themeToggle');
if (themeToggle) {
themeToggle.addEventListener('click', () => {
document.body.classList.toggle('dark-mode');
themeToggle.innerHTML = document.body.classList.contains('dark-mode')
? '<i class="fas fa-sun"></i>'
: '<i class="fas fa-moon"></i>';
});
}
// 初始化用户管理相关的功能
initUserManagement();
// 添加用户表单提交
document.getElementById('addUserForm').addEventListener('submit', async function(e) {
e.preventDefault();
const userData = {
username: document.getElementById('name').value.trim(),
email: document.getElementById('email').value.trim(),
password: document.getElementById('password').value,
phone_number: document.getElementById('phoneNumber').value.trim() || null,
role: document.getElementById('role').value,
is_active: false,
email_verified: false,
phone_verified: false
};
try {
// 前端验证
validateUserData(userData);
const token = localStorage.getItem('access_token');
const response = await fetch(`${API_URL}/register`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(userData)
});
if (!response.ok) {
const errorData = await response.json();
console.log('服务器返回的完整错误信息:', JSON.stringify(errorData, null, 2));
if (Array.isArray(errorData.detail)) {
const firstError = errorData.detail[0];
throw new Error(firstError?.msg || lang[selectedLang].addUserFailed);
}
throw new Error(errorData.detail || lang[selectedLang].addUserFailed);
}
fetchUsers();
closeAddUserModal();
this.reset(); // 重置表单
showToast(lang[selectedLang].addUserSuccess, 'success');
} catch (error) {
console.error(lang[selectedLang].addUserError, error);
showToast(error.message || lang[selectedLang].addUserFailed, 'error');
}
});
// 初始化用户管理相关的功能
initUserManagement();
});
function initUserManagement() {
// 初始化用户管理相关的功能
checkLoginStatus();
showFeature();
}
const API_URL = 'https://user.obscura.work/user';
let currentUser = null;
// 获取用户信息的函数
async function fetchUserInfo() {
try {
const token = localStorage.getItem('access_token');
if (!token) {
throw new Error('No access token found');
}
const response = await makeAuthenticatedRequest(`${API_URL}/me`);
if (!response.ok) {
throw new Error('Failed to fetch user info');
}
const userInfo = await response.json();
return userInfo; // 直接返回用户信息
} catch (error) {
console.error('Error fetching user info:', error);
logout();
return null;
}
}
// 更新用户显示的函数
function updateUserDisplay() {
const userName = document.getElementById('userName');
const userAvatar = document.getElementById('userAvatar');
const dashboardContent = document.getElementById('dashboardContent');
const usersContent = document.getElementById('usersContent');
if (currentUser) {
userName.textContent = currentUser.username;
userAvatar.textContent = currentUser.username.charAt(0).toUpperCase();
if (dashboardContent) dashboardContent.style.display = 'block';
if (usersContent) usersContent.style.display = 'none';
if (userCenterContent) userCenterContent.style.display = 'none';
} else {
userName.textContent = 'Guest';
userAvatar.textContent = 'G';
if (dashboardContent) dashboardContent.style.display = 'none';
if (usersContent) usersContent.style.display = 'none';
if (userCenterContent) userCenterContent.style.display = 'none';
}
}
// 登出函数
async function logout() {
const token = localStorage.getItem('access_token');
if (token) {
try {
await fetch(`${API_URL}/logout`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
},
});
} catch (error) {
console.error(lang[selectedLang].logoutError, error);
}
}
localStorage.removeItem('access_token');
currentUser = null;
updateUserDisplay();
window.location.href = 'login.html'; // 重定向到登录页面
}
// 显示仪表板
async function showDashboard() {
try {
const userInfo = await fetchUserInfo();
if (!userInfo) {
alert(lang[selectedLang].pleaseLoginToViewDashboard);
window.location.href = 'login.html'; // 重定向到登录页面
return;
}
// 检查邮箱验证状态
const emailVerified = userInfo.email_verified === 'True' || userInfo.email_verified === true;
console.log('Email Verified:', emailVerified); // 打印邮箱验证状态
if (!emailVerified) {
alert(lang[selectedLang].pleaseVerifyEmail);
showUserCenterSection('profile'); // 跳转到个人资料页面
return;
}
// 如果用户已登录且邮箱已验证,继续显示仪表盘
const pageTitle = document.getElementById('pageTitle');
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 showApiDocs = document.getElementById('showApiDocs');
const apiDocsContent = document.getElementById('apiDocsContent');
;
if (!userInfo) {
console.error('No user info found');
return;
}
if (apiDocsContent) apiDocsContent.style.display = 'none'
if (showApiDocs) showApiDocs.style.display = 'none';
if (userCenterContent) userCenterContent.style.display = 'none';
if (chatContent) chatContent.style.display = 'none';
if (featureContent) featureContent.style.display = 'none';
if (usersContent) usersContent.style.display = 'none';
if (pageTitle) pageTitle.textContent = lang[selectedLang].dashboard;
if (dashboardContent) {
dashboardContent.style.display = 'block';
dashboardContent.innerHTML = `
<h2>Hello, ${userInfo.username}!</h2>
<section class="dashboard-cards">
<div class="card">
<i class="fas fa-users card-icon"></i>
<h3 class="card-title" data-i18n="totalUsers">Total Users</h3>
<p class="card-value" id="totalUsers">Loading...</p>
</div>
<div class="card">
<i class="fas fa-user-plus card-icon"></i>
<h3 class="card-title" data-i18n="newUsersToday">New Users Today</h3>
<p class="card-value" id="newUsers">Loading...</p>
</div>
<div class="card">
<i class="fas fa-user-check card-icon"></i>
<h3 class="card-title" data-i18n="activeUsers">Active Users</h3>
<p class="card-value" id="activeUsers">Loading...</p>
</div>
</section>
<section class="recent-activities">
<h3 data-i18n="recentNewUsers">Recent New Users</h3>
<ul class="activity-list" id="activityList">
<li data-i18n="loading">Loading recent users...</li>
</ul>
</section>
<div class="chart-container">
<canvas id="userChart"></canvas>
</div>
`;
fetchDashboardData();
}
} catch (error) {
console.error('Error in showDashboard:', error);
alert(lang[selectedLang].errorGettingUserInfo);
}
}
// 获取仪表板数据
async function fetchDashboardData() {
try {
const response = await makeAuthenticatedRequest(`${API_URL}/dashboard`);
if (!response.ok) {
throw new Error(lang[selectedLang].fetchDashboardDataFailed);
}
const data = await response.json();
updateDashboardUI(data);
fetchUserStats();
} catch (error) {
console.error('Error fetching dashboard data:', error);
alert(lang[selectedLang].fetchDashboardDataFailed);
}
}
async function fetchUserStats() {
try {
const response = await makeAuthenticatedRequest(`${API_URL}/user-stats`);
if (!response.ok) {
throw new Error('Failed to fetch user stats');
}
const data = await response.json();
createUserChart(data);
} catch (error) {
console.error('Error fetching user stats:', error);
}
}
// 更新仪表板UI
function updateDashboardUI(data) {
try {
// 更新用户统计数据
const totalUsersEl = document.getElementById('totalUsers');
const newUsersEl = document.getElementById('newUsers');
const activeUsersEl = document.getElementById('activeUsers');
if (totalUsersEl) totalUsersEl.textContent = data.total_users;
if (newUsersEl) newUsersEl.textContent = data.new_users_today;
if (activeUsersEl) activeUsersEl.textContent = data.active_users;
// 更新最近用户列表
const activityList = document.getElementById('activityList');
if (activityList) {
activityList.innerHTML = '';
if (data.recent_users && Array.isArray(data.recent_users) && data.recent_users.length > 0) {
data.recent_users.forEach(user => {
const li = document.createElement('li');
li.className = 'activity-item';
li.innerHTML = `
<i class="fas fa-user-plus activity-icon"></i>
<div class="activity-details">
<p class="activity-description">${lang[selectedLang].newUser}: ${user.username}</p>
<p class="activity-time">${formatActivityTime(user.created_at)}</p>
</div>
`;
activityList.appendChild(li);
});
} else {
console.warn(lang[selectedLang].noRecentUsersData);
activityList.innerHTML = `<li>${lang[selectedLang].noRecentUserActivities}</li>`;
}
} else {
console.error(lang[selectedLang].activityListNotFound);
}
} catch (error) {
console.error('Error updating dashboard UI:', error);
}
}
// 格式化活动时间
function formatActivityTime(timestamp) {
const date = new Date(timestamp);
const now = new Date();
const diffTime = Math.abs(now - date);
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
const diffHours = Math.floor(diffTime / (1000 * 60 * 60));
const diffMinutes = Math.floor(diffTime / (1000 * 60));
if (diffDays > 0) {
return `${diffDays} ${lang[selectedLang].daysAgo}`;
} else if (diffHours > 0) {
return `${diffHours} ${lang[selectedLang].hoursAgo}`;
} else if (diffMinutes > 0) {
return `${diffMinutes} ${lang[selectedLang].minutesAgo}`;
} else {
return lang[selectedLang].justNow;
}
}
// 创建用户统计图表
function createUserChart(userStats) {
const ctx = document.getElementById('userChart').getContext('2d');
new Chart(ctx, {
type: 'line',
data: {
labels: userStats.dates,
datasets: [{
label: lang[selectedLang].newUsersToday,
data: userStats.daily_new_users,
backgroundColor: 'rgba(102, 126, 234, 0.2)',
borderColor: 'rgba(102, 126, 234, 1)',
borderWidth: 2,
pointBackgroundColor: 'rgba(102, 126, 234, 1)',
pointBorderColor: '#fff',
pointHoverBackgroundColor: '#fff',
pointHoverBorderColor: 'rgba(102, 126, 234, 1)'
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
title: {
display: true,
text: lang[selectedLang].numberOfNewUsers
}
},
x: {
title: {
display: true,
text: lang[selectedLang].date
}
}
}
}
});
}
// 显示用户列表
let currentPage = 1;
const usersPerPage = 10; // 每页显示的用户数量
let allUsers = []; // 存储所有用户数据
// 在文件开头添加变量
let searchUserTimeout = null;
// 修改 showUsers 函数,添加搜索框初始化
async function showUsers() {
try {
const userInfo = await fetchUserInfo();
if (!userInfo) {
alert(lang[selectedLang].pleaseLoginToViewUserList);
window.location.href = 'login.html';
return;
}
// 检查邮箱验证状态
const emailVerified = userInfo.email_verified === 'True' || userInfo.email_verified === true;
if (!emailVerified) {
alert(lang[selectedLang].pleaseCompleteEmailVerification);
showUserCenterSection('profile');
return;
}
// 更新页面内容
const pageTitle = document.getElementById('pageTitle');
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 apiDocsContent = document.getElementById('apiDocsContent');
if (apiDocsContent) apiDocsContent.style.display = 'none';
if (dashboardContent) dashboardContent.style.display = 'none';
if (featureContent) featureContent.style.display = 'none';
if (usersContent) usersContent.style.display = 'block';
if (chatContent) chatContent.style.display = 'none';
if (userCenterContent) userCenterContent.style.display = 'none';
if (pageTitle) pageTitle.innerText = lang[selectedLang].userManagement;
// 初始化搜索功能
initializeUserSearch();
fetchUsers();
} catch (error) {
console.error('Error in showUsers:', error);
alert(lang[selectedLang].errorGettingUserInfo);
}
}
// 修改搜索初始化函数
function initializeUserSearch() {
const searchInput = document.getElementById('userSearch');
const searchIcon = document.querySelector('.user-search-icon');
if (searchInput) {
// 添加占位符提示
searchInput.placeholder = '搜索用户名、邮箱或角色(admin/user)...';
// 输入事件处理(实时搜索)
searchInput.addEventListener('input', function(e) {
if (searchUserTimeout) {
clearTimeout(searchUserTimeout);
}
searchUserTimeout = setTimeout(() => {
searchUsers(e.target.value);
}, 300);
});
// 回车键事件处理
searchInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
e.preventDefault();
searchUsers(this.value);
}
});
// 点击搜索图标事件处理
if (searchIcon) {
searchIcon.addEventListener('click', function() {
searchUsers(searchInput.value);
});
searchIcon.style.cursor = 'pointer';
}
}
}
// 修改搜索用户函数
async function searchUsers(query) {
try {
const token = localStorage.getItem('access_token');
const response = await fetch(`${API_URL}/api/users-search?query=${encodeURIComponent(query)}`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`
}
});
if (!response.ok) {
throw new Error('搜索失败');
}
const searchResults = await response.json();
allUsers = searchResults; // 更新全局用户列表
currentPage = 1; // 重置到第一页
updatePagination();
displayUsers();
// 如果没有搜索结果,显示提示
if (searchResults.length === 0) {
showToast('未找到匹配的用户', 'info');
}
} catch (error) {
console.error('搜索用户时出错:', error);
showToast(error.message || lang[selectedLang].searchFailed, 'error');
}
}
// 获取用户列表
async function fetchUsers() {
try {
const response = await makeAuthenticatedRequest(`${API_URL}/api/users`);
if (!response.ok) {
throw new Error('Network response was not ok');
}
allUsers = await response.json();
updatePagination();
displayUsers();
return allUsers;
} catch (error) {
console.error('Error fetching users:', error);
alert('Failed to fetch users. Please try again.');
return [];
}
}
// 更新分页信息
function updatePagination() {
const totalPages = Math.ceil(allUsers.length / usersPerPage);
const paginationDiv = document.querySelector('.pagination');
let paginationHTML = `
<a href="#" class="page-nav" onclick="changePage(-1)">&lt;</a>
`;
const showPages = (start, end) => {
for (let i = start; i <= end; i++) {
paginationHTML += `
<a href="#" class="page-number ${i === currentPage ? 'active' : ''}" onclick="goToPage(${i})">${i}</a>
`;
}
};
if (totalPages <= 7) {
showPages(1, totalPages);
} else {
if (currentPage <= 3) {
showPages(1, 5);
paginationHTML += `<span class="page-ellipsis">...</span>`;
paginationHTML += `<a href="#" class="page-number" onclick="goToPage(${totalPages})">${totalPages}</a>`;
} else if (currentPage >= totalPages - 2) {
paginationHTML += `<a href="#" class="page-number" onclick="goToPage(1)">1</a>`;
paginationHTML += `<span class="page-ellipsis">...</span>`;
showPages(totalPages - 4, totalPages);
} else {
paginationHTML += `<a href="#" class="page-number" onclick="goToPage(1)">1</a>`;
paginationHTML += `<span class="page-ellipsis">...</span>`;
showPages(currentPage - 1, currentPage + 1);
paginationHTML += `<span class="page-ellipsis">...</span>`;
paginationHTML += `<a href="#" class="page-number" onclick="goToPage(${totalPages})">${totalPages}</a>`;
}
}
paginationHTML += `
<a href="#" class="page-nav" onclick="changePage(1)">&gt;</a>
`;
paginationDiv.innerHTML = paginationHTML;
}
// 切换到指定页面
function goToPage(page) {
currentPage = page;
displayUsers();
updatePagination();
}
// 修改changePage函数
function changePage(direction) {
const totalPages = Math.ceil(allUsers.length / usersPerPage);
currentPage += direction;
if (currentPage < 1) currentPage = 1;
if (currentPage > totalPages) currentPage = totalPages;
displayUsers();
updatePagination();
}
// 修改显示用户函数
function displayUsers() {
const start = (currentPage - 1) * usersPerPage;
const end = start + usersPerPage;
const pageUsers = allUsers.slice(start, end);
const userTableBody = document.getElementById('userTableBody');
userTableBody.innerHTML = '';
if (pageUsers.length === 0) {
// 果没有用户数据,显示一个提示行
const emptyRow = document.createElement('tr');
emptyRow.innerHTML = `
<td colspan="5" class="text-center">${lang[selectedLang].noUsersFound}</td>
`;
userTableBody.appendChild(emptyRow);
return;
}
pageUsers.forEach((user, index) => {
const row = document.createElement('tr');
row.innerHTML = `
<td>${(currentPage - 1) * usersPerPage + index + 1}</td>
<td>${user.username}</td>
<td>${user.email}</td>
<td>${user.role}</td>
<td>
<button onclick="editUser('${user.id}')"><i class="fas fa-edit"></i></button>
<button onclick="deleteUser('${user.id}')"><i class="fas fa-trash"></i></button>
</td>
`;
userTableBody.appendChild(row);
});
}
// 修改编辑用户函数
async function editUser(id) {
try {
// 获取当前用户信息
const userResponse = await makeAuthenticatedRequest(`${API_URL}/api/users/${id}`);
if (!userResponse.ok) {
throw new Error(lang[selectedLang].getUserInfoFailed);
}
const userData = await userResponse.json();
// 创建编辑表单
const formContent = `
<div class="edit-user-form">
<div class="form-group">
<label for="edit-username">${lang[selectedLang].username}</label>
<input type="text" id="edit-username" value="${userData.username}" required>
</div>
<div class="form-group">
<label for="edit-role">${lang[selectedLang].role}</label>
<select id="edit-role">
<option value="admin" ${userData.role === 'admin' ? 'selected' : ''}>Admin</option>
<option value="user" ${userData.role === 'user' ? 'selected' : ''}>User</option>
</select>
</div>
</div>
`;
// 使用自定义对话框显示表单
const result = await showCustomDialog(
lang[selectedLang].editUser,
formContent,
lang[selectedLang].save,
lang[selectedLang].cancel
);
if (result && result.confirmed) {
const { username, role } = result.formData;
if (!username) {
throw new Error(lang[selectedLang].usernameRequired);
}
const response = await makeAuthenticatedRequest(`${API_URL}/edit/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
username,
role
})
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.detail || lang[selectedLang].updateUserFailed);
}
await fetchUsers(); // 重新获取用户列表
showToast(lang[selectedLang].userInfoUpdateSuccess, 'success');
}
} catch (error) {
console.error('Error editing user:', error);
showToast(error.message || lang[selectedLang].updateUserFailed, 'error');
}
}
// 修改自定义对话框函数
function showCustomDialog(title, content, confirmText, cancelText) {
return new Promise((resolve) => {
const dialog = document.createElement('div');
dialog.className = 'custom-dialog';
dialog.innerHTML = `
<div class="dialog-content">
<h2>${title}</h2>
<div>${content}</div>
<div class="dialog-buttons">
<button class="btn-confirm">${confirmText}</button>
<button class="btn-cancel">${cancelText}</button>
</div>
</div>
`;
document.body.appendChild(dialog);
const confirmBtn = dialog.querySelector('.btn-confirm');
const cancelBtn = dialog.querySelector('.btn-cancel');
confirmBtn.addEventListener('click', () => {
const username = dialog.querySelector('#edit-username')?.value;
const role = dialog.querySelector('#edit-role')?.value;
document.body.removeChild(dialog);
resolve({
confirmed: true,
formData: { username, role }
});
});
cancelBtn.addEventListener('click', () => {
document.body.removeChild(dialog);
resolve({ confirmed: false });
});
});
}
// 修改显示提示消息函数
function showToast(message, type = 'info') {
try {
// 移除已存在的提示框
const existingToast = document.querySelector('.toast-container');
if (existingToast && existingToast.parentNode) {
existingToast.parentNode.removeChild(existingToast);
}
// 创建提示框容器
const toastContainer = document.createElement('div');
toastContainer.className = 'toast-container';
// 创建提示框
const toast = document.createElement('div');
toast.className = `toast toast-${type}`;
// 添加图标
const icon = document.createElement('i');
switch (type) {
case 'success':
icon.className = 'fas fa-check-circle';
break;
case 'error':
icon.className = 'fas fa-exclamation-circle';
break;
case 'info':
icon.className = 'fas fa-info-circle';
break;
case 'warning':
icon.className = 'fas fa-exclamation-triangle';
break;
}
// 创建消息文本元素
const messageText = document.createElement('span');
messageText.textContent = message;
// 组装提示框
toast.appendChild(icon);
toast.appendChild(messageText);
toastContainer.appendChild(toast);
document.body.appendChild(toastContainer);
// 添加显示动画
setTimeout(() => {
toast.classList.add('show');
setTimeout(() => {
toast.classList.remove('show');
setTimeout(() => {
if (toastContainer && toastContainer.parentNode) {
toastContainer.parentNode.removeChild(toastContainer);
}
}, 300);
}, 3000);
}, 100);
} catch (error) {
console.error('Error showing toast:', error);
}
}
// 修改删除用户函数
async function deleteUser(id) {
try {
const confirmed = await showCustomDialog(
lang[selectedLang].confirmDelete,
`<p>${lang[selectedLang].confirmDeleteUserMessage}</p>`,
lang[selectedLang].delete,
lang[selectedLang].cancel
);
if (confirmed) {
const response = await makeAuthenticatedRequest(`${API_URL}/api/users/${id}`, {
method: 'DELETE'
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.detail || lang[selectedLang].deleteUserFailed);
}
await fetchUsers(); // 重新获取用户列表
showToast(lang[selectedLang].userDeleteSuccess, 'success');
}
} catch (error) {
console.error('Error deleting user:', error);
showToast(error.message || lang[selectedLang].deleteUserFailed, 'error');
}
}
// 打开添加用户模态框
function openAddUserModal() {
const modal = document.getElementById('addUserModal');
const form = document.getElementById('addUserForm');
form.reset(); // 确保表单是空的
modal.style.display = 'block';
}
// 关闭添加用户模态框
function closeAddUserModal() {
const modal = document.getElementById('addUserModal');
const form = document.getElementById('addUserForm');
modal.style.display = 'none';
form.reset(); // 重置表单
}
async function makeAuthenticatedRequest(url, options = {}) {
const token = localStorage.getItem('access_token');
if (!token) {
throw new Error('未找到token');
}
const headers = new Headers(options.headers || {});
headers.append('Authorization', `Bearer ${token}`);
const response = await fetch(url, { ...options, headers });
if (response.status === 401) {
logout();
throw new Error('未授权');
}
return response;
}
// 导出用户数据为CSV
function exportUserData() {
if (!allUsers || allUsers.length === 0) {
alert(lang[selectedLang].noUserDataToExport);
return;
}
const csvContent = "data:text/csv;charset=utf-8,"
+ "用户名,邮箱,角色\n"
+ allUsers.map(user => `${user.username},${user.email},${user.role}`).join("\n");
const encodedUri = encodeURI(csvContent);
const link = document.createElement("a");
link.setAttribute("href", encodedUri);
link.setAttribute("download", "user_data.csv");
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
// 添加数据验证函数
function validateUserData(userData) {
// 用户名验证:只验证必填
if (!userData.username || userData.username.trim() === '') {
throw new Error('用户名不能为空');
}
// 邮箱验证:必填且唯一,格式正确
if (!userData.email || !userData.email.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)) {
throw new Error('请输入有效的邮箱地址');
}
// 密码验证
if (!userData.password || userData.password.length < 6) {
throw new Error('密码至少需要6个字符');
}
// 手机号验证:如果提供则必须格式正确
if (userData.phone_number && userData.phone_number !== '') {
if (!userData.phone_number.match(/^\d{11}$/)) {
throw new Error('请输入有效的手机号码');
}
}
}
+461
View File
@@ -0,0 +1,461 @@
const API_ENDPOINT = 'https://dev.obscura.work/v1';
// 确保 selectedLang 在 module.js 中可用
if (typeof selectedLang === 'undefined') {
selectedLang = 'zh'; // 如果未定义,设置默认值
}
async function showFeature() {
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 ) {
alert(lang[selectedLang].pleaseVerifyEmail);
showUserCenterSection('profile');
return;
}
// 如果用户已登录且邮箱已验证,继续显示功能
const chatContent = document.getElementById('chatContent');
const dashboardContent = document.getElementById('dashboardContent');
const usersContent = document.getElementById('usersContent');
const pageTitle = document.getElementById('pageTitle');
const featureContent = document.getElementById('featureContent');
const userCenterContent = document.getElementById('userCenterContent');
const apiKeyInput = document.getElementById('apiKeyInput');
const showApiDocs = document.getElementById('showApiDocs');
const apiDocsContent = document.getElementById('apiDocsContent');
if (apiDocsContent) apiDocsContent.style.display = 'none';
if (featureContent) featureContent.style.display = 'flex';
if (chatContent) chatContent.style.display = 'none';
if (dashboardContent) dashboardContent.style.display = 'none';
if (usersContent) usersContent.style.display = 'none';
if (userCenterContent) userCenterContent.style.display = 'none';
if (showApiDocs) showApiDocs.style.display = 'none';
if (pageTitle) pageTitle.innerText = lang[selectedLang].featureDemo;
} catch (error) {
console.error('Error in showFeature:', error);
alert(lang[selectedLang].errorGettingUserInfo);
}
}
function displayTokenUsage(data) {
console.log(lang[selectedLang].tokenUsage, data);
}
document.addEventListener('DOMContentLoaded', function() {
// 首先声明所有需要的变量
const aiInput = document.getElementById('aiInput');
const fileInput = document.getElementById('fileInput');
const fileInputButton = document.getElementById('fileInputButton');
const takePictureButton = document.getElementById('takePictureButton');
const recordVideoButton = document.getElementById('recordVideoButton');
const clearButton = document.getElementById('clearButton');
const stopRecordingButton = document.getElementById('stopRecordingButton');
const previewArea = document.getElementById('previewArea');
const resultArea = document.getElementById('resultArea');
const loadingIndicator = document.getElementById('loadingIndicator');
const poseButton = document.getElementById('poseButton');
const fallButton = document.getElementById('fallButton');
const cpmButton = document.getElementById('cpmButton');
const objectButton = document.getElementById('objectButton');
const faceButton = document.getElementById('faceButton');
const compareButton = document.getElementById('compareButton');
const mediapipeButton = document.getElementById('mediapipeButton');
const qwenvlButton = document.getElementById('qwenvlButton');
const cpmanalyzeButton = document.getElementById('cpmanalyzeButton');
const qwenvlanalyzeButton = document.getElementById('qwenvlanalyzeButton');
let mediaRecorder;
let recordedChunks = [];
// 定义任务类型
const TASK_TYPES = {
POSE: 'pose',
CPM: 'cpm',
QWENVL: 'qwenvl',
YOLO: 'yolo',
FALL: 'fall',
FACE: 'face',
MEDIAPIPE: 'mediapipe',
COMPARE: 'compare',
CPM_ANALYZE: 'cpm_analyze',
QWENVL_ANALYZE: 'qwenvl_analyze'
};
// 然后添加事件监听器
if (fileInput) {
fileInput.addEventListener('change', function(e) {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
previewArea.innerHTML = '';
if (file.type.startsWith('image/')) {
const img = document.createElement('img');
img.src = e.target.result;
previewArea.appendChild(img);
} else if (file.type.startsWith('video/')) {
const video = document.createElement('video');
video.src = e.target.result;
video.controls = true;
previewArea.appendChild(video);
}
}
reader.readAsDataURL(file);
}
});
}
if (fileInputButton) {
fileInputButton.addEventListener('click', function() {
fileInput.click();
});
}
function uploadFile(taskType) {
const file = fileInput.files[0];
console.log("Selected file:", file);
const apiKey = apiKeyInput.value.trim();
console.log("API Key:", apiKey);
if (!file) {
alert(lang[selectedLang].pleaseSelectFile);
return;
}
if (!apiKey) {
alert(lang[selectedLang].pleaseEnterApiKey);
return;
}
loadingIndicator.style.display = 'block';
disableAllButtons();
const formData = new FormData();
formData.append('file', file);
console.log(`Sending request to: ${API_ENDPOINT}/${taskType}`);
fetch(`${API_ENDPOINT}/${taskType}`, {
method: 'POST',
body: formData,
headers: {
'Authorization': `Bearer ${apiKey}`
}
})
.then(response => response.json())
.then(data => {
console.log('Upload response:', data);
if (data.task_id) {
pollResult(data.task_id, apiKey, taskType);
displayTokenUsage(data);
}
})
.catch(error => {
console.error('Error:', error);
alert(lang[selectedLang].errorUploadingFile);
loadingIndicator.style.display = 'none';
enableAllButtons();
});
}
// 新的轮询函数
function pollResult(taskId, apiKey, taskType) {
const pollInterval = setInterval(() => {
fetch(`${API_ENDPOINT}/result/${taskId}`, {
headers: {
'Authorization': `Bearer ${apiKey}`
}
})
.then(response => response.json())
.then(data => {
console.log('Poll response:', data);
if (data.status === 'completed') {
clearInterval(pollInterval);
addResultCard(getAnalysisTitle(taskType), data);
if (taskType !== 'cpm' && taskType !== 'qwenvl' && taskType !== 'qwenvl_analyze' && taskType !== 'cpm_analyze') {
fetchAnnotatedFile(API_ENDPOINT, taskId, apiKey);
}
loadingIndicator.style.display = 'none';
enableAllButtons();
} else if (data.status === 'failed') {
clearInterval(pollInterval);
alert(lang[selectedLang].processingFailed);
loadingIndicator.style.display = 'none';
enableAllButtons();
}
})
.catch(error => {
console.error('Error:', error);
clearInterval(pollInterval);
alert(lang[selectedLang].errorGettingResults);
loadingIndicator.style.display = 'none';
enableAllButtons();
});
}, 2000); // 每2秒轮询一次
}
// 辅助函数
function getAnalysisTitle(analysisType) {
switch(analysisType) {
case 'pose': return '姿态分析';
case 'fall': return '跌倒检测';
case 'cpm': return 'MiniCPM内容分析';
case 'object': return '目标检测';
case 'face': return '人脸检测';
case 'compare': return '面部特征提取';
case 'mediapipe': return 'mediapipe面部识别';
case 'qwenvl': return 'Qwen内容分析';
case 'qwenvl_analyze': return 'qwenvl_OCR分析';
case 'cpm_analyze': return 'cpm_OCR分析';
default: return '分析结果';
}
}
function fetchAnnotatedFile(apiEndpoint, taskId, apiKey) {
fetch(`${apiEndpoint}/annotated/${taskId}`, {
headers: {
'Authorization': `Bearer ${apiKey}`
}
})
.then(response => {
if (response.ok) {
return response.blob();
}
if (response.status === 404) {
throw new Error(lang[selectedLang].resultImageNotFound);
}
throw new Error('Network response was not ok.');
})
.then(blob => {
const url = URL.createObjectURL(blob);
updatePreviewWithAnnotated(url, `result_${taskId}.png`);
})
.catch(error => {
console.error('Error:', error);
if (error.message === lang[selectedLang].resultImageNotFound) {
alert(lang[selectedLang].resultImageNotFound);
} else {
alert(lang[selectedLang].errorGettingResultImage);
}
});
}
function addResultCard(title, data) {
const card = document.createElement('div');
card.className = 'result-card';
card.innerHTML = `
<h3>${title}</h3>
<pre>${JSON.stringify(data, null, 2)}</pre>
`;
resultArea.innerHTML = `<h3>${lang[selectedLang].detectionResult}</h3>`; // 保留标题
resultArea.appendChild(card);
}
function updatePreviewWithAnnotated(url, filename) {
previewArea.innerHTML = '';
const isImage = filename.match(/\.(jpg|jpeg|png|gif)$/i);
if (isImage) {
const img = document.createElement('img');
img.src = url;
img.alt = "Annotated Image";
img.style.maxWidth = "100%";
previewArea.appendChild(img);
} else {
const video = document.createElement('video');
video.src = url;
video.controls = true;
video.style.maxWidth = "100%";
previewArea.appendChild(video);
}
}
function clearAll() {
aiInput.value = '';
fileInput.value = '';
previewArea.innerHTML = '<p>请输入图片URL或上传图片</p>';
resultArea.innerHTML = '<h3>检测结果</h3>';
loadingIndicator.style.display = 'none';
enableAllButtons();
}
function disableAllButtons() {
poseButton.disabled = true;
fallButton.disabled = true;
cpmButton.disabled = true;
objectButton.disabled = true;
faceButton.disabled = true;
qwenvlButton.disabled = true;
mediapipeButton.disabled = true;
takePictureButton.disabled = true;
recordVideoButton.disabled = true;
compareButton.disabled = true;
qwenvlanalyzeButton.disabled =true;
cpmanalyzeButton.disabled = true
}
function enableAllButtons() {
poseButton.disabled = false;
fallButton.disabled = false;
cpmButton.disabled = false;
objectButton.disabled = false;
faceButton.disabled = false;
qwenvlButton.disabled = false;
mediapipeButton.disabled = false;
takePictureButton.disabled = false;
recordVideoButton.disabled = false;
compareButton.disabled = false;
qwenvlanalyzeButton.disabled =false;
cpmanalyzeButton.disabled = false
}
function takePicture() {
navigator.mediaDevices.getUserMedia({ video: true })
.then(function(stream) {
const video = document.createElement('video');
video.srcObject = stream;
video.play();
video.onloadedmetadata = function(e) {
const canvas = document.createElement('canvas');
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height);
stream.getTracks().forEach(track => track.stop());
previewArea.innerHTML = '';
const img = document.createElement('img');
img.src = canvas.toDataURL('image/jpeg');
previewArea.appendChild(img);
canvas.toBlob(function(blob) {
const file = new File([blob], "camera_photo.jpg", { type: "image/jpeg" });
const dataTransfer = new DataTransfer();
dataTransfer.items.add(file);
fileInput.files = dataTransfer.files;
}, 'image/jpeg');
};
})
.catch(function(err) {
console.error(lang[selectedLang].cameraAccessFailed, err);
alert(lang[selectedLang].cameraPermissionDenied);
});
}
function startRecording() {
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
.then(function(stream) {
previewArea.innerHTML = '<video id="liveVideo" autoplay muted></video>';
const liveVideo = document.getElementById('liveVideo');
liveVideo.srcObject = stream;
mediaRecorder = new MediaRecorder(stream);
mediaRecorder.ondataavailable = function(e) {
if (e.data.size > 0) {
recordedChunks.push(e.data);
}
};
mediaRecorder.onstop = function() {
const blob = new Blob(recordedChunks, { type: 'video/webm' });
const videoURL = URL.createObjectURL(blob);
previewArea.innerHTML = '<video id="recordedVideo" controls></video>';
const recordedVideo = document.getElementById('recordedVideo');
recordedVideo.src = videoURL;
const file = new File([blob], "camera_video.webm", { type: "video/webm" });
const dataTransfer = new DataTransfer();
dataTransfer.items.add(file);
fileInput.files = dataTransfer.files;
stream.getTracks().forEach(track => track.stop());
};
mediaRecorder.start();
recordVideoButton.style.display = 'none';
stopRecordingButton.style.display = 'block';
setTimeout(() => {
if (mediaRecorder.state === 'recording') {
stopRecording();
}
}, 15000);
})
.catch(function(err) {
console.error(lang[selectedLang].cameraAccessFailed, err);
alert(lang[selectedLang].cameraPermissionDenied);
});
}
function stopRecording() {
if (mediaRecorder && mediaRecorder.state === 'recording') {
mediaRecorder.stop();
recordVideoButton.style.display = 'block';
stopRecordingButton.style.display = 'none';
}
}
// 最后添加其他按钮的事件监听器
if (recordVideoButton) recordVideoButton.addEventListener('click', startRecording);
if (stopRecordingButton) stopRecordingButton.addEventListener('click', stopRecording);
if (clearButton) clearButton.addEventListener('click', clearAll);
if (takePictureButton) takePictureButton.addEventListener('click', takePicture);
if (poseButton) poseButton.addEventListener('click', () => uploadFile(TASK_TYPES.POSE));
if (fallButton) fallButton.addEventListener('click', () => uploadFile(TASK_TYPES.FALL));
if (objectButton) objectButton.addEventListener('click', () => uploadFile(TASK_TYPES.YOLO));
if (faceButton) faceButton.addEventListener('click', () => uploadFile(TASK_TYPES.FACE));
if (compareButton) compareButton.addEventListener('click', () => uploadFile(TASK_TYPES.COMPARE));
if (mediapipeButton) mediapipeButton.addEventListener('click', () => uploadFile(TASK_TYPES.MEDIAPIPE));
if (cpmButton) cpmButton.addEventListener('click', () => uploadFile(TASK_TYPES.CPM));
if (qwenvlButton) qwenvlButton.addEventListener('click', () => uploadFile(TASK_TYPES.QWENVL));
if (qwenvlanalyzeButton) qwenvlanalyzeButton.addEventListener('click', () => uploadFile(TASK_TYPES.QWENVL_ANALYZE));
if (cpmanalyzeButton) cpmanalyzeButton.addEventListener('click', () => uploadFile(TASK_TYPES.CPM_ANALYZE));
});
// 获取用户信息的函数
async function fetchUserInfo() {
try {
const token = localStorage.getItem('access_token');
if (!token) {
console.error('No access token found');
return null;
}
const response = await fetch(`${BASE_URL}/me`, {
headers: {
'Authorization': `Bearer ${token}`
}
});
if (!response.ok) {
throw new Error('Failed to fetch user info');
}
const userInfo = await response.json();
return userInfo;
} catch (error) {
console.error('Error fetching user info:', error);
// 可以在这里处理错误,比如清除 token 并重定向到登录页面
localStorage.removeItem('access_token');
window.location.href = 'login.html'; // 重定向到登录页面
return null;
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 944 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

+1972
View File
File diff suppressed because it is too large Load Diff
+22
View File
@@ -0,0 +1,22 @@
DB_USER=minio
DB_PASSWORD=pif2M_v95k_qT5t
DB_HOST=sh-cdb-4ln6r8y0.sql.tencentcdb.com
DB_NAME=minio
DB_PORT=28234
# Redis 配置
REDIS_HOST=10.0.4.17
REDIS_PORT=13003
REDIS_DB=0
REDIS_API_DB=2
REDIS_API_USAGE_DB=3
REDIS_PASSWORD=Obscura@2024
REDIS_REGISTER_DB=1
SMTP_SERVER = "smtp.feishu.cn"
SMTP_PORT = 465
SMTP_USERNAME = "dev@obscura.work"
SMTP_PASSWORD = "95RsVmJUAqgwzJDV"
SUPABASE_URL=https://ihrczmpgwyiqjgsdsxkr.supabase.co
SUPABASE_SERVICE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImlocmN6bXBnd3lpcWpnc2RzeGtyIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTczMTYzNDg2MSwiZXhwIjoyMDQ3MjEwODYxfQ.uaySGQTyYRgQYCoQJCXAK5AYNqVjfbjGmvd-ko3C8Ww
+1019
View File
File diff suppressed because it is too large Load Diff
+10
View File
@@ -0,0 +1,10 @@
fastapi
sqlalchemy
passlib
python-dotenv
redis
uvicorn
pydantic[email]
python-multipart
cryptography
supabase