import React, { useContext, useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { API, copy, isRoot, isAdmin, showError, showInfo, showSuccess, renderQuota, renderQuotaWithPrompt, stringToColor, onGitHubOAuthClicked, onOIDCClicked, onLinuxDOOAuthClicked, renderModelTag, getModelCategories } from '../../helpers'; import Turnstile from 'react-turnstile'; import { UserContext } from '../../context/User'; import { Avatar, Banner, Button, Card, Empty, Image, Input, Layout, Modal, Skeleton, Space, Tag, Typography, Collapsible, Radio, RadioGroup, AutoComplete, Checkbox, Tabs, TabPane, } from '@douyinfe/semi-ui'; import { IllustrationNoContent, IllustrationNoContentDark } from '@douyinfe/semi-illustrations'; import { IconMail, IconLock, IconShield, IconUser, IconSetting, IconBell, IconGithubLogo, IconKey, IconDelete, IconChevronDown, IconChevronUp, } from '@douyinfe/semi-icons'; import { SiTelegram, SiWechat, SiLinux } from 'react-icons/si'; import { Bell, Shield, Webhook, Globe, Settings, UserPlus, ShieldCheck } from 'lucide-react'; import TelegramLoginButton from 'react-telegram-login'; import { useTranslation } from 'react-i18next'; const PersonalSetting = () => { const [userState, userDispatch] = useContext(UserContext); let navigate = useNavigate(); const { t } = useTranslation(); const [inputs, setInputs] = useState({ wechat_verification_code: '', email_verification_code: '', email: '', self_account_deletion_confirmation: '', original_password: '', set_new_password: '', set_new_password_confirmation: '', }); const [status, setStatus] = useState({}); const [showChangePasswordModal, setShowChangePasswordModal] = useState(false); const [showWeChatBindModal, setShowWeChatBindModal] = useState(false); const [showEmailBindModal, setShowEmailBindModal] = useState(false); const [showAccountDeleteModal, setShowAccountDeleteModal] = useState(false); const [turnstileEnabled, setTurnstileEnabled] = useState(false); const [turnstileSiteKey, setTurnstileSiteKey] = useState(''); const [turnstileToken, setTurnstileToken] = useState(''); const [loading, setLoading] = useState(false); const [disableButton, setDisableButton] = useState(false); const [countdown, setCountdown] = useState(30); const [systemToken, setSystemToken] = useState(''); const [models, setModels] = useState([]); const [isModelsExpanded, setIsModelsExpanded] = useState(() => { // Initialize from localStorage if available const savedState = localStorage.getItem('modelsExpanded'); return savedState ? JSON.parse(savedState) : false; }); const [activeModelCategory, setActiveModelCategory] = useState('all'); const MODELS_DISPLAY_COUNT = 25; // 默认显示的模型数量 const [notificationSettings, setNotificationSettings] = useState({ warningType: 'email', warningThreshold: 100000, webhookUrl: '', webhookSecret: '', notificationEmail: '', acceptUnsetModelRatioModel: false, }); const [modelsLoading, setModelsLoading] = useState(true); const [showWebhookDocs, setShowWebhookDocs] = useState(true); const [isDarkMode, setIsDarkMode] = useState(false); // 检测暗色模式 useEffect(() => { const checkDarkMode = () => { const isDark = document.documentElement.classList.contains('dark') || window.matchMedia('(prefers-color-scheme: dark)').matches; setIsDarkMode(isDark); }; checkDarkMode(); // 监听主题变化 const observer = new MutationObserver(checkDarkMode); observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] }); const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); mediaQuery.addListener(checkDarkMode); return () => { observer.disconnect(); mediaQuery.removeListener(checkDarkMode); }; }, []); useEffect(() => { let status = localStorage.getItem('status'); if (status) { status = JSON.parse(status); setStatus(status); if (status.turnstile_check) { setTurnstileEnabled(true); setTurnstileSiteKey(status.turnstile_site_key); } } getUserData().then((res) => { console.log(userState); }); loadModels().then(); }, []); useEffect(() => { let countdownInterval = null; if (disableButton && countdown > 0) { countdownInterval = setInterval(() => { setCountdown(countdown - 1); }, 1000); } else if (countdown === 0) { setDisableButton(false); setCountdown(30); } return () => clearInterval(countdownInterval); // Clean up on unmount }, [disableButton, countdown]); useEffect(() => { if (userState?.user?.setting) { const settings = JSON.parse(userState.user.setting); setNotificationSettings({ warningType: settings.notify_type || 'email', warningThreshold: settings.quota_warning_threshold || 500000, webhookUrl: settings.webhook_url || '', webhookSecret: settings.webhook_secret || '', notificationEmail: settings.notification_email || '', acceptUnsetModelRatioModel: settings.accept_unset_model_ratio_model || false, }); } }, [userState?.user?.setting]); // Save models expanded state to localStorage whenever it changes useEffect(() => { localStorage.setItem('modelsExpanded', JSON.stringify(isModelsExpanded)); }, [isModelsExpanded]); const handleInputChange = (name, value) => { setInputs((inputs) => ({ ...inputs, [name]: value })); }; const generateAccessToken = async () => { const res = await API.get('/api/user/token'); const { success, message, data } = res.data; if (success) { setSystemToken(data); await copy(data); showSuccess(t('令牌已重置并已复制到剪贴板')); } else { showError(message); } }; const getUserData = async () => { let res = await API.get(`/api/user/self`); const { success, message, data } = res.data; if (success) { userDispatch({ type: 'login', payload: data }); } else { showError(message); } }; const loadModels = async () => { setModelsLoading(true); try { let res = await API.get(`/api/user/models`); const { success, message, data } = res.data; if (success) { if (data != null) { setModels(data); } } else { showError(message); } } catch (error) { showError(t('加载模型列表失败')); } finally { setModelsLoading(false); } }; const handleSystemTokenClick = async (e) => { e.target.select(); await copy(e.target.value); showSuccess(t('系统令牌已复制到剪切板')); }; const deleteAccount = async () => { if (inputs.self_account_deletion_confirmation !== userState.user.username) { showError(t('请输入你的账户名以确认删除!')); return; } const res = await API.delete('/api/user/self'); const { success, message } = res.data; if (success) { showSuccess(t('账户已删除!')); await API.get('/api/user/logout'); userDispatch({ type: 'logout' }); localStorage.removeItem('user'); navigate('/login'); } else { showError(message); } }; const bindWeChat = async () => { if (inputs.wechat_verification_code === '') return; const res = await API.get( `/api/oauth/wechat/bind?code=${inputs.wechat_verification_code}`, ); const { success, message } = res.data; if (success) { showSuccess(t('微信账户绑定成功!')); setShowWeChatBindModal(false); } else { showError(message); } }; const changePassword = async () => { if (inputs.original_password === '') { showError(t('请输入原密码!')); return; } if (inputs.set_new_password === '') { showError(t('请输入新密码!')); return; } if (inputs.original_password === inputs.set_new_password) { showError(t('新密码需要和原密码不一致!')); return; } if (inputs.set_new_password !== inputs.set_new_password_confirmation) { showError(t('两次输入的密码不一致!')); return; } const res = await API.put(`/api/user/self`, { original_password: inputs.original_password, password: inputs.set_new_password, }); const { success, message } = res.data; if (success) { showSuccess(t('密码修改成功!')); setShowWeChatBindModal(false); } else { showError(message); } setShowChangePasswordModal(false); }; const sendVerificationCode = async () => { if (inputs.email === '') { showError(t('请输入邮箱!')); return; } setDisableButton(true); if (turnstileEnabled && turnstileToken === '') { showInfo(t('请稍后几秒重试,Turnstile 正在检查用户环境!')); return; } setLoading(true); const res = await API.get( `/api/verification?email=${inputs.email}&turnstile=${turnstileToken}`, ); const { success, message } = res.data; if (success) { showSuccess(t('验证码发送成功,请检查邮箱!')); } else { showError(message); } setLoading(false); }; const bindEmail = async () => { if (inputs.email_verification_code === '') { showError(t('请输入邮箱验证码!')); return; } setLoading(true); const res = await API.get( `/api/oauth/email/bind?email=${inputs.email}&code=${inputs.email_verification_code}`, ); const { success, message } = res.data; if (success) { showSuccess(t('邮箱账户绑定成功!')); setShowEmailBindModal(false); userState.user.email = inputs.email; } else { showError(message); } setLoading(false); }; const getUsername = () => { if (userState.user) { return userState.user.username; } else { return 'null'; } }; const getAvatarText = () => { const username = getUsername(); if (username && username.length > 0) { // 获取前两个字符,支持中文和英文 return username.slice(0, 2).toUpperCase(); } return 'NA'; }; const copyText = async (text) => { if (await copy(text)) { showSuccess(t('已复制:') + text); } else { // setSearchKeyword(text); Modal.error({ title: t('无法复制到剪贴板,请手动复制'), content: text }); } }; const handleNotificationSettingChange = (type, value) => { setNotificationSettings((prev) => ({ ...prev, [type]: value.target ? value.target.value : value, // 处理 Radio 事件对象 })); }; const saveNotificationSettings = async () => { try { const res = await API.put('/api/user/setting', { notify_type: notificationSettings.warningType, quota_warning_threshold: parseFloat( notificationSettings.warningThreshold, ), webhook_url: notificationSettings.webhookUrl, webhook_secret: notificationSettings.webhookSecret, notification_email: notificationSettings.notificationEmail, accept_unset_model_ratio_model: notificationSettings.acceptUnsetModelRatioModel, }); if (res.data.success) { showSuccess(t('通知设置已更新')); await getUserData(); } else { showError(res.data.message); } } catch (error) { showError(t('更新通知设置失败')); } }; return (
{`{
"type": "quota_exceed", // 通知类型
"title": "标题", // 通知标题
"content": "通知内容", // 通知内容,支持 {{value}} 变量占位符
"values": ["值1", "值2"], // 按顺序替换content中的 {{value}} 占位符
"timestamp": 1739950503 // 时间戳
}
示例:
{
"type": "quota_exceed",
"title": "额度预警通知",
"content": "您的额度即将用尽,当前剩余额度为 {{value}}",
"values": ["$0.99"],
"timestamp": 1739950503
}`}
{t('微信扫码关注公众号,输入「验证码」获取验证码(三分钟内有效)')}