Merge remote-tracking branch 'origin/main'
# Conflicts: # web/src/components/PersonalSetting.js
This commit is contained in:
commit
377da2dfcb
11
README.md
11
README.md
@ -86,10 +86,10 @@ _✨ 通过标准的 OpenAI API 格式访问所有的大模型,开箱即用
|
|||||||
+ [x] [360 智脑](https://ai.360.cn)
|
+ [x] [360 智脑](https://ai.360.cn)
|
||||||
2. 支持配置镜像以及众多第三方代理服务:
|
2. 支持配置镜像以及众多第三方代理服务:
|
||||||
+ [x] [OpenAI-SB](https://openai-sb.com)
|
+ [x] [OpenAI-SB](https://openai-sb.com)
|
||||||
|
+ [x] [CloseAI](https://console.closeai-asia.com/r/2412)
|
||||||
+ [x] [API2D](https://api2d.com/r/197971)
|
+ [x] [API2D](https://api2d.com/r/197971)
|
||||||
+ [x] [OhMyGPT](https://aigptx.top?aff=uFpUl2Kf)
|
+ [x] [OhMyGPT](https://aigptx.top?aff=uFpUl2Kf)
|
||||||
+ [x] [AI Proxy](https://aiproxy.io/?i=OneAPI) (邀请码:`OneAPI`)
|
+ [x] [AI Proxy](https://aiproxy.io/?i=OneAPI) (邀请码:`OneAPI`)
|
||||||
+ [x] [CloseAI](https://console.closeai-asia.com/r/2412)
|
|
||||||
+ [x] 自定义渠道:例如各种未收录的第三方代理服务
|
+ [x] 自定义渠道:例如各种未收录的第三方代理服务
|
||||||
3. 支持通过**负载均衡**的方式访问多个渠道。
|
3. 支持通过**负载均衡**的方式访问多个渠道。
|
||||||
4. 支持 **stream 模式**,可以通过流式传输实现打字机效果。
|
4. 支持 **stream 模式**,可以通过流式传输实现打字机效果。
|
||||||
@ -226,6 +226,13 @@ docker run --name chatgpt-web -d -p 3002:3002 -e OPENAI_API_BASE_URL=https://ope
|
|||||||
|
|
||||||
注意修改端口号、`OPENAI_API_BASE_URL` 和 `OPENAI_API_KEY`。
|
注意修改端口号、`OPENAI_API_BASE_URL` 和 `OPENAI_API_KEY`。
|
||||||
|
|
||||||
|
#### QChatGPT - QQ机器人
|
||||||
|
项目主页:https://github.com/RockChinQ/QChatGPT
|
||||||
|
|
||||||
|
根据文档完成部署后,在`config.py`设置配置项`openai_config`的`reverse_proxy`为 One API 后端地址,设置`api_key`为 One API 生成的key,并在配置项`completion_api_params`的`model`参数设置为 One API 支持的模型名称。
|
||||||
|
|
||||||
|
可安装 [Switcher 插件](https://github.com/RockChinQ/Switcher)在运行时切换所使用的模型。
|
||||||
|
|
||||||
### 部署到第三方平台
|
### 部署到第三方平台
|
||||||
<details>
|
<details>
|
||||||
<summary><strong>部署到 Sealos </strong></summary>
|
<summary><strong>部署到 Sealos </strong></summary>
|
||||||
@ -379,4 +386,4 @@ https://openai.justsong.cn
|
|||||||
|
|
||||||
同样适用于基于本项目的二开项目。
|
同样适用于基于本项目的二开项目。
|
||||||
|
|
||||||
依据 MIT 协议,使用者需自行承担使用本项目的风险与责任,本开源项目开发者与此无关。
|
依据 MIT 协议,使用者需自行承担使用本项目的风险与责任,本开源项目开发者与此无关。
|
||||||
|
@ -79,6 +79,14 @@ func getGitHubUserInfoByCode(code string) (*GitHubUser, error) {
|
|||||||
|
|
||||||
func GitHubOAuth(c *gin.Context) {
|
func GitHubOAuth(c *gin.Context) {
|
||||||
session := sessions.Default(c)
|
session := sessions.Default(c)
|
||||||
|
state := c.Query("state")
|
||||||
|
if state == "" || session.Get("oauth_state") == nil || state != session.Get("oauth_state").(string) {
|
||||||
|
c.JSON(http.StatusForbidden, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": "state is empty or not same",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
username := session.Get("username")
|
username := session.Get("username")
|
||||||
if username != nil {
|
if username != nil {
|
||||||
GitHubBind(c)
|
GitHubBind(c)
|
||||||
@ -205,3 +213,22 @@ func GitHubBind(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GenerateOAuthCode(c *gin.Context) {
|
||||||
|
session := sessions.Default(c)
|
||||||
|
state := common.GetRandomString(12)
|
||||||
|
session.Set("oauth_state", state)
|
||||||
|
err := session.Save()
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": true,
|
||||||
|
"message": "",
|
||||||
|
"data": state,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -357,6 +357,15 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode {
|
|||||||
isStream = isStream || strings.HasPrefix(resp.Header.Get("Content-Type"), "text/event-stream")
|
isStream = isStream || strings.HasPrefix(resp.Header.Get("Content-Type"), "text/event-stream")
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
if preConsumedQuota != 0 {
|
||||||
|
go func() {
|
||||||
|
// return pre-consumed quota
|
||||||
|
err := model.PostConsumeTokenQuota(tokenId, -preConsumedQuota)
|
||||||
|
if err != nil {
|
||||||
|
common.SysError("error return pre-consumed quota: " + err.Error())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
return relayErrorHandler(resp)
|
return relayErrorHandler(resp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ func SetApiRouter(router *gin.Engine) {
|
|||||||
apiRouter.GET("/reset_password", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.SendPasswordResetEmail)
|
apiRouter.GET("/reset_password", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.SendPasswordResetEmail)
|
||||||
apiRouter.POST("/user/reset", middleware.CriticalRateLimit(), controller.ResetPassword)
|
apiRouter.POST("/user/reset", middleware.CriticalRateLimit(), controller.ResetPassword)
|
||||||
apiRouter.GET("/oauth/github", middleware.CriticalRateLimit(), controller.GitHubOAuth)
|
apiRouter.GET("/oauth/github", middleware.CriticalRateLimit(), controller.GitHubOAuth)
|
||||||
|
apiRouter.GET("/oauth/state", middleware.CriticalRateLimit(), controller.GenerateOAuthCode)
|
||||||
apiRouter.GET("/oauth/wechat", middleware.CriticalRateLimit(), controller.WeChatAuth)
|
apiRouter.GET("/oauth/wechat", middleware.CriticalRateLimit(), controller.WeChatAuth)
|
||||||
apiRouter.GET("/oauth/wechat/bind", middleware.CriticalRateLimit(), middleware.UserAuth(), controller.WeChatBind)
|
apiRouter.GET("/oauth/wechat/bind", middleware.CriticalRateLimit(), middleware.UserAuth(), controller.WeChatBind)
|
||||||
apiRouter.GET("/oauth/email/bind", middleware.CriticalRateLimit(), middleware.UserAuth(), controller.EmailBind)
|
apiRouter.GET("/oauth/email/bind", middleware.CriticalRateLimit(), middleware.UserAuth(), controller.EmailBind)
|
||||||
|
@ -13,8 +13,8 @@ const GitHubOAuth = () => {
|
|||||||
|
|
||||||
let navigate = useNavigate();
|
let navigate = useNavigate();
|
||||||
|
|
||||||
const sendCode = async (code, count) => {
|
const sendCode = async (code, state, count) => {
|
||||||
const res = await API.get(`/api/oauth/github?code=${code}`);
|
const res = await API.get(`/api/oauth/github?code=${code}&state=${state}`);
|
||||||
const { success, message, data } = res.data;
|
const { success, message, data } = res.data;
|
||||||
if (success) {
|
if (success) {
|
||||||
if (message === 'bind') {
|
if (message === 'bind') {
|
||||||
@ -36,13 +36,14 @@ const GitHubOAuth = () => {
|
|||||||
count++;
|
count++;
|
||||||
setPrompt(`出现错误,第 ${count} 次重试中...`);
|
setPrompt(`出现错误,第 ${count} 次重试中...`);
|
||||||
await new Promise((resolve) => setTimeout(resolve, count * 2000));
|
await new Promise((resolve) => setTimeout(resolve, count * 2000));
|
||||||
await sendCode(code, count);
|
await sendCode(code, state, count);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let code = searchParams.get('code');
|
let code = searchParams.get('code');
|
||||||
sendCode(code, 0).then();
|
let state = searchParams.get('state');
|
||||||
|
sendCode(code, state, 0).then();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -3,6 +3,7 @@ import { Button, Divider, Form, Grid, Header, Image, Message, Modal, Segment } f
|
|||||||
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
|
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
|
||||||
import { UserContext } from '../context/User';
|
import { UserContext } from '../context/User';
|
||||||
import { API, getLogo, showError, showSuccess } from '../helpers';
|
import { API, getLogo, showError, showSuccess } from '../helpers';
|
||||||
|
import { getOAuthState, onGitHubOAuthClicked } from './utils';
|
||||||
|
|
||||||
const LoginForm = () => {
|
const LoginForm = () => {
|
||||||
const [inputs, setInputs] = useState({
|
const [inputs, setInputs] = useState({
|
||||||
@ -31,12 +32,6 @@ const LoginForm = () => {
|
|||||||
|
|
||||||
const [showWeChatLoginModal, setShowWeChatLoginModal] = useState(false);
|
const [showWeChatLoginModal, setShowWeChatLoginModal] = useState(false);
|
||||||
|
|
||||||
const onGitHubOAuthClicked = () => {
|
|
||||||
window.open(
|
|
||||||
`https://github.com/login/oauth/authorize?client_id=${status.github_client_id}&scope=user:email`
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onWeChatLoginClicked = () => {
|
const onWeChatLoginClicked = () => {
|
||||||
setShowWeChatLoginModal(true);
|
setShowWeChatLoginModal(true);
|
||||||
};
|
};
|
||||||
@ -131,7 +126,7 @@ const LoginForm = () => {
|
|||||||
circular
|
circular
|
||||||
color='black'
|
color='black'
|
||||||
icon='github'
|
icon='github'
|
||||||
onClick={onGitHubOAuthClicked}
|
onClick={()=>onGitHubOAuthClicked(status.github_client_id)}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
|
@ -4,378 +4,373 @@ import { Link, useNavigate } from 'react-router-dom';
|
|||||||
import { API, copy, showError, showInfo, showNotice, showSuccess } from '../helpers';
|
import { API, copy, showError, showInfo, showNotice, showSuccess } from '../helpers';
|
||||||
import Turnstile from 'react-turnstile';
|
import Turnstile from 'react-turnstile';
|
||||||
import { UserContext } from '../context/User';
|
import { UserContext } from '../context/User';
|
||||||
|
import { onGitHubOAuthClicked } from './utils';
|
||||||
|
|
||||||
const PersonalSetting = () => {
|
const PersonalSetting = () => {
|
||||||
const [userState, userDispatch] = useContext(UserContext);
|
const [userState, userDispatch] = useContext(UserContext);
|
||||||
let navigate = useNavigate();
|
let navigate = useNavigate();
|
||||||
|
|
||||||
const [inputs, setInputs] = useState({
|
const [inputs, setInputs] = useState({
|
||||||
wechat_verification_code: '',
|
wechat_verification_code: '',
|
||||||
email_verification_code: '',
|
email_verification_code: '',
|
||||||
email: '',
|
email: '',
|
||||||
self_account_deletion_confirmation: ''
|
self_account_deletion_confirmation: ''
|
||||||
});
|
});
|
||||||
const [status, setStatus] = useState({});
|
const [status, setStatus] = useState({});
|
||||||
const [showWeChatBindModal, setShowWeChatBindModal] = useState(false);
|
const [showWeChatBindModal, setShowWeChatBindModal] = useState(false);
|
||||||
const [showEmailBindModal, setShowEmailBindModal] = useState(false);
|
const [showEmailBindModal, setShowEmailBindModal] = useState(false);
|
||||||
const [showAccountDeleteModal, setShowAccountDeleteModal] = useState(false);
|
const [showAccountDeleteModal, setShowAccountDeleteModal] = useState(false);
|
||||||
const [turnstileEnabled, setTurnstileEnabled] = useState(false);
|
const [turnstileEnabled, setTurnstileEnabled] = useState(false);
|
||||||
const [turnstileSiteKey, setTurnstileSiteKey] = useState('');
|
const [turnstileSiteKey, setTurnstileSiteKey] = useState('');
|
||||||
const [turnstileToken, setTurnstileToken] = useState('');
|
const [turnstileToken, setTurnstileToken] = useState('');
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [disableButton, setDisableButton] = useState(false);
|
const [disableButton, setDisableButton] = useState(false);
|
||||||
const [countdown, setCountdown] = useState(30);
|
const [countdown, setCountdown] = useState(30);
|
||||||
const [affLink, setAffLink] = useState("");
|
const [affLink, setAffLink] = useState("");
|
||||||
const [systemToken, setSystemToken] = useState("");
|
const [systemToken, setSystemToken] = useState("");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let status = localStorage.getItem('status');
|
let status = localStorage.getItem('status');
|
||||||
if (status) {
|
if (status) {
|
||||||
status = JSON.parse(status);
|
status = JSON.parse(status);
|
||||||
setStatus(status);
|
setStatus(status);
|
||||||
if (status.turnstile_check) {
|
if (status.turnstile_check) {
|
||||||
setTurnstileEnabled(true);
|
setTurnstileEnabled(true);
|
||||||
setTurnstileSiteKey(status.turnstile_site_key);
|
setTurnstileSiteKey(status.turnstile_site_key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let countdownInterval = null;
|
let countdownInterval = null;
|
||||||
if (disableButton && countdown > 0) {
|
if (disableButton && countdown > 0) {
|
||||||
countdownInterval = setInterval(() => {
|
countdownInterval = setInterval(() => {
|
||||||
setCountdown(countdown - 1);
|
setCountdown(countdown - 1);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
} else if (countdown === 0) {
|
} else if (countdown === 0) {
|
||||||
setDisableButton(false);
|
setDisableButton(false);
|
||||||
setCountdown(30);
|
setCountdown(30);
|
||||||
}
|
}
|
||||||
return () => clearInterval(countdownInterval); // Clean up on unmount
|
return () => clearInterval(countdownInterval); // Clean up on unmount
|
||||||
}, [disableButton, countdown]);
|
}, [disableButton, countdown]);
|
||||||
|
|
||||||
const handleInputChange = (e, { name, value }) => {
|
const handleInputChange = (e, { name, value }) => {
|
||||||
setInputs((inputs) => ({ ...inputs, [name]: value }));
|
setInputs((inputs) => ({ ...inputs, [name]: value }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateAccessToken = async () => {
|
const generateAccessToken = async () => {
|
||||||
const res = await API.get('/api/user/token');
|
const res = await API.get('/api/user/token');
|
||||||
const { success, message, data } = res.data;
|
const { success, message, data } = res.data;
|
||||||
if (success) {
|
if (success) {
|
||||||
setSystemToken(data);
|
setSystemToken(data);
|
||||||
setAffLink("");
|
setAffLink("");
|
||||||
await copy(data);
|
await copy(data);
|
||||||
showSuccess(`令牌已重置并已复制到剪贴板`);
|
showSuccess(`令牌已重置并已复制到剪贴板`);
|
||||||
} else {
|
} else {
|
||||||
showError(message);
|
showError(message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAffLink = async () => {
|
const getAffLink = async () => {
|
||||||
const res = await API.get('/api/user/aff');
|
const res = await API.get('/api/user/aff');
|
||||||
const { success, message, data } = res.data;
|
const { success, message, data } = res.data;
|
||||||
if (success) {
|
if (success) {
|
||||||
let link = `${window.location.origin}/register?aff=${data}`;
|
let link = `${window.location.origin}/register?aff=${data}`;
|
||||||
setAffLink(link);
|
setAffLink(link);
|
||||||
setSystemToken("");
|
setSystemToken("");
|
||||||
await copy(link);
|
await copy(link);
|
||||||
showSuccess(`邀请链接已复制到剪切板`);
|
showSuccess(`邀请链接已复制到剪切板`);
|
||||||
} else {
|
} else {
|
||||||
showError(message);
|
showError(message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAffLinkClick = async (e) => {
|
const handleAffLinkClick = async (e) => {
|
||||||
e.target.select();
|
e.target.select();
|
||||||
await copy(e.target.value);
|
await copy(e.target.value);
|
||||||
showSuccess(`邀请链接已复制到剪切板`);
|
showSuccess(`邀请链接已复制到剪切板`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSystemTokenClick = async (e) => {
|
const handleSystemTokenClick = async (e) => {
|
||||||
e.target.select();
|
e.target.select();
|
||||||
await copy(e.target.value);
|
await copy(e.target.value);
|
||||||
showSuccess(`系统令牌已复制到剪切板`);
|
showSuccess(`系统令牌已复制到剪切板`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteAccount = async () => {
|
const deleteAccount = async () => {
|
||||||
if (inputs.self_account_deletion_confirmation !== userState.user.username) {
|
if (inputs.self_account_deletion_confirmation !== userState.user.username) {
|
||||||
showError('请输入你的账户名以确认删除!');
|
showError('请输入你的账户名以确认删除!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await API.delete('/api/user/self');
|
const res = await API.delete('/api/user/self');
|
||||||
const { success, message } = res.data;
|
const { success, message } = res.data;
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
showSuccess('账户已删除!');
|
showSuccess('账户已删除!');
|
||||||
await API.get('/api/user/logout');
|
await API.get('/api/user/logout');
|
||||||
userDispatch({ type: 'logout' });
|
userDispatch({ type: 'logout' });
|
||||||
localStorage.removeItem('user');
|
localStorage.removeItem('user');
|
||||||
navigate('/login');
|
navigate('/login');
|
||||||
} else {
|
} else {
|
||||||
showError(message);
|
showError(message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const bindWeChat = async () => {
|
const bindWeChat = async () => {
|
||||||
if (inputs.wechat_verification_code === '') return;
|
if (inputs.wechat_verification_code === '') return;
|
||||||
const res = await API.get(
|
const res = await API.get(
|
||||||
`/api/oauth/wechat/bind?code=${inputs.wechat_verification_code}`
|
`/api/oauth/wechat/bind?code=${inputs.wechat_verification_code}`
|
||||||
);
|
|
||||||
const { success, message } = res.data;
|
|
||||||
if (success) {
|
|
||||||
showSuccess('微信账户绑定成功!');
|
|
||||||
setShowWeChatBindModal(false);
|
|
||||||
} else {
|
|
||||||
showError(message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const openGitHubOAuth = () => {
|
|
||||||
window.open(
|
|
||||||
`https://github.com/login/oauth/authorize?client_id=${status.github_client_id}&scope=user:email`
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const sendVerificationCode = async () => {
|
|
||||||
setDisableButton(true);
|
|
||||||
if (inputs.email === '') return;
|
|
||||||
if (turnstileEnabled && turnstileToken === '') {
|
|
||||||
showInfo('请稍后几秒重试,Turnstile 正在检查用户环境!');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setLoading(true);
|
|
||||||
const res = await API.get(
|
|
||||||
`/api/verification?email=${inputs.email}&turnstile=${turnstileToken}`
|
|
||||||
);
|
|
||||||
const { success, message } = res.data;
|
|
||||||
if (success) {
|
|
||||||
showSuccess('验证码发送成功,请检查邮箱!');
|
|
||||||
} else {
|
|
||||||
showError(message);
|
|
||||||
}
|
|
||||||
setLoading(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const bindEmail = async () => {
|
|
||||||
if (inputs.email_verification_code === '') 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('邮箱账户绑定成功!');
|
|
||||||
setShowEmailBindModal(false);
|
|
||||||
} else {
|
|
||||||
showError(message);
|
|
||||||
}
|
|
||||||
setLoading(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div style={{ lineHeight: '40px' }}>
|
|
||||||
<Header as='h3'>通用设置</Header>
|
|
||||||
<Message>
|
|
||||||
注意,此处生成的令牌用于系统管理,而非用于请求 OpenAI 相关的服务,请知悉。
|
|
||||||
</Message>
|
|
||||||
<Button as={Link} to={`/user/edit/`}>
|
|
||||||
更新个人信息
|
|
||||||
</Button>
|
|
||||||
<Button onClick={generateAccessToken}>生成系统访问令牌</Button>
|
|
||||||
<Button onClick={getAffLink}>复制邀请链接</Button>
|
|
||||||
<Button onClick={() => {
|
|
||||||
setShowAccountDeleteModal(true);
|
|
||||||
}}>删除个人账户</Button>
|
|
||||||
|
|
||||||
{systemToken && (
|
|
||||||
<Form.Input
|
|
||||||
fluid
|
|
||||||
readOnly
|
|
||||||
value={systemToken}
|
|
||||||
onClick={handleSystemTokenClick}
|
|
||||||
style={{ marginTop: '10px' }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{affLink && (
|
|
||||||
<Form.Input
|
|
||||||
fluid
|
|
||||||
readOnly
|
|
||||||
value={affLink}
|
|
||||||
onClick={handleAffLinkClick}
|
|
||||||
style={{ marginTop: '10px' }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Divider />
|
|
||||||
<Header as='h3'>账号绑定</Header>
|
|
||||||
{
|
|
||||||
status.wechat_login && (
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
setShowWeChatBindModal(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
绑定微信账号
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
<Modal
|
|
||||||
onClose={() => setShowWeChatBindModal(false)}
|
|
||||||
onOpen={() => setShowWeChatBindModal(true)}
|
|
||||||
open={showWeChatBindModal}
|
|
||||||
size={'mini'}
|
|
||||||
>
|
|
||||||
<Modal.Content>
|
|
||||||
<Modal.Description>
|
|
||||||
<Image src={status.wechat_qrcode} fluid />
|
|
||||||
<div style={{ textAlign: 'center' }}>
|
|
||||||
<p>
|
|
||||||
微信扫码关注公众号,输入「验证码」获取验证码(三分钟内有效)
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Form size='large'>
|
|
||||||
<Form.Input
|
|
||||||
fluid
|
|
||||||
placeholder='验证码'
|
|
||||||
name='wechat_verification_code'
|
|
||||||
value={inputs.wechat_verification_code}
|
|
||||||
onChange={handleInputChange}
|
|
||||||
/>
|
|
||||||
<Button color='' fluid size='large' onClick={bindWeChat}>
|
|
||||||
绑定
|
|
||||||
</Button>
|
|
||||||
</Form>
|
|
||||||
</Modal.Description>
|
|
||||||
</Modal.Content>
|
|
||||||
</Modal>
|
|
||||||
{
|
|
||||||
status.github_oauth && (
|
|
||||||
<Button onClick={openGitHubOAuth}>绑定 GitHub 账号</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
setShowEmailBindModal(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
绑定邮箱地址
|
|
||||||
</Button>
|
|
||||||
<Modal
|
|
||||||
onClose={() => setShowEmailBindModal(false)}
|
|
||||||
onOpen={() => setShowEmailBindModal(true)}
|
|
||||||
open={showEmailBindModal}
|
|
||||||
size={'tiny'}
|
|
||||||
style={{ maxWidth: '450px' }}
|
|
||||||
>
|
|
||||||
<Modal.Header>绑定邮箱地址</Modal.Header>
|
|
||||||
<Modal.Content>
|
|
||||||
<Modal.Description>
|
|
||||||
<Form size='large'>
|
|
||||||
<Form.Input
|
|
||||||
fluid
|
|
||||||
placeholder='输入邮箱地址'
|
|
||||||
onChange={handleInputChange}
|
|
||||||
name='email'
|
|
||||||
type='email'
|
|
||||||
action={
|
|
||||||
<Button onClick={sendVerificationCode} disabled={disableButton || loading}>
|
|
||||||
{disableButton ? `重新发送(${countdown})` : '获取验证码'}
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Form.Input
|
|
||||||
fluid
|
|
||||||
placeholder='验证码'
|
|
||||||
name='email_verification_code'
|
|
||||||
value={inputs.email_verification_code}
|
|
||||||
onChange={handleInputChange}
|
|
||||||
/>
|
|
||||||
{turnstileEnabled ? (
|
|
||||||
<Turnstile
|
|
||||||
sitekey={turnstileSiteKey}
|
|
||||||
onVerify={(token) => {
|
|
||||||
setTurnstileToken(token);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginTop: '1rem' }}>
|
|
||||||
<Button
|
|
||||||
color=''
|
|
||||||
fluid
|
|
||||||
size='large'
|
|
||||||
onClick={bindEmail}
|
|
||||||
loading={loading}
|
|
||||||
>
|
|
||||||
确认绑定
|
|
||||||
</Button>
|
|
||||||
<div style={{ width: '1rem' }}></div>
|
|
||||||
<Button
|
|
||||||
fluid
|
|
||||||
size='large'
|
|
||||||
onClick={() => setShowEmailBindModal(false)}
|
|
||||||
>
|
|
||||||
取消
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Form>
|
|
||||||
</Modal.Description>
|
|
||||||
</Modal.Content>
|
|
||||||
</Modal>
|
|
||||||
<Modal
|
|
||||||
onClose={() => setShowAccountDeleteModal(false)}
|
|
||||||
onOpen={() => setShowAccountDeleteModal(true)}
|
|
||||||
open={showAccountDeleteModal}
|
|
||||||
size={'tiny'}
|
|
||||||
style={{ maxWidth: '450px' }}
|
|
||||||
>
|
|
||||||
<Modal.Header>危险操作</Modal.Header>
|
|
||||||
<Modal.Content>
|
|
||||||
<Message>您正在删除自己的帐户,将清空所有数据且不可恢复</Message>
|
|
||||||
<Modal.Description>
|
|
||||||
<Form size='large'>
|
|
||||||
<Form.Input
|
|
||||||
fluid
|
|
||||||
placeholder={`输入你的账户名 ${userState?.user?.username} 以确认删除`}
|
|
||||||
name='self_account_deletion_confirmation'
|
|
||||||
value={inputs.self_account_deletion_confirmation}
|
|
||||||
onChange={handleInputChange}
|
|
||||||
/>
|
|
||||||
{turnstileEnabled ? (
|
|
||||||
<Turnstile
|
|
||||||
sitekey={turnstileSiteKey}
|
|
||||||
onVerify={(token) => {
|
|
||||||
setTurnstileToken(token);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginTop: '1rem' }}>
|
|
||||||
<Button
|
|
||||||
color='red'
|
|
||||||
fluid
|
|
||||||
size='large'
|
|
||||||
onClick={deleteAccount}
|
|
||||||
loading={loading}
|
|
||||||
>
|
|
||||||
确认删除
|
|
||||||
</Button>
|
|
||||||
<div style={{ width: '1rem' }}></div>
|
|
||||||
<Button
|
|
||||||
fluid
|
|
||||||
size='large'
|
|
||||||
onClick={() => setShowAccountDeleteModal(false)}
|
|
||||||
>
|
|
||||||
取消
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Form>
|
|
||||||
</Modal.Description>
|
|
||||||
</Modal.Content>
|
|
||||||
</Modal>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
|
const { success, message } = res.data;
|
||||||
|
if (success) {
|
||||||
|
showSuccess('微信账户绑定成功!');
|
||||||
|
setShowWeChatBindModal(false);
|
||||||
|
} else {
|
||||||
|
showError(message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const sendVerificationCode = async () => {
|
||||||
|
setDisableButton(true);
|
||||||
|
if (inputs.email === '') return;
|
||||||
|
if (turnstileEnabled && turnstileToken === '') {
|
||||||
|
showInfo('请稍后几秒重试,Turnstile 正在检查用户环境!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLoading(true);
|
||||||
|
const res = await API.get(
|
||||||
|
`/api/verification?email=${inputs.email}&turnstile=${turnstileToken}`
|
||||||
|
);
|
||||||
|
const { success, message } = res.data;
|
||||||
|
if (success) {
|
||||||
|
showSuccess('验证码发送成功,请检查邮箱!');
|
||||||
|
} else {
|
||||||
|
showError(message);
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const bindEmail = async () => {
|
||||||
|
if (inputs.email_verification_code === '') 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('邮箱账户绑定成功!');
|
||||||
|
setShowEmailBindModal(false);
|
||||||
|
} else {
|
||||||
|
showError(message);
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ lineHeight: '40px' }}>
|
||||||
|
<Header as='h3'>通用设置</Header>
|
||||||
|
<Message>
|
||||||
|
注意,此处生成的令牌用于系统管理,而非用于请求 OpenAI 相关的服务,请知悉。
|
||||||
|
</Message>
|
||||||
|
<Button as={Link} to={`/user/edit/`}>
|
||||||
|
更新个人信息
|
||||||
|
</Button>
|
||||||
|
<Button onClick={generateAccessToken}>生成系统访问令牌</Button>
|
||||||
|
<Button onClick={getAffLink}>复制邀请链接</Button>
|
||||||
|
<Button onClick={() => {
|
||||||
|
setShowAccountDeleteModal(true);
|
||||||
|
}}>删除个人账户</Button>
|
||||||
|
|
||||||
|
{systemToken && (
|
||||||
|
<Form.Input
|
||||||
|
fluid
|
||||||
|
readOnly
|
||||||
|
value={systemToken}
|
||||||
|
onClick={handleSystemTokenClick}
|
||||||
|
style={{ marginTop: '10px' }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{affLink && (
|
||||||
|
<Form.Input
|
||||||
|
fluid
|
||||||
|
readOnly
|
||||||
|
value={affLink}
|
||||||
|
onClick={handleAffLinkClick}
|
||||||
|
style={{ marginTop: '10px' }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Divider />
|
||||||
|
<Header as='h3'>账号绑定</Header>
|
||||||
|
{
|
||||||
|
status.wechat_login && (
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setShowWeChatBindModal(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
绑定微信账号
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
<Modal
|
||||||
|
onClose={() => setShowWeChatBindModal(false)}
|
||||||
|
onOpen={() => setShowWeChatBindModal(true)}
|
||||||
|
open={showWeChatBindModal}
|
||||||
|
size={'mini'}
|
||||||
|
>
|
||||||
|
<Modal.Content>
|
||||||
|
<Modal.Description>
|
||||||
|
<Image src={status.wechat_qrcode} fluid />
|
||||||
|
<div style={{ textAlign: 'center' }}>
|
||||||
|
<p>
|
||||||
|
微信扫码关注公众号,输入「验证码」获取验证码(三分钟内有效)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Form size='large'>
|
||||||
|
<Form.Input
|
||||||
|
fluid
|
||||||
|
placeholder='验证码'
|
||||||
|
name='wechat_verification_code'
|
||||||
|
value={inputs.wechat_verification_code}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
|
<Button color='' fluid size='large' onClick={bindWeChat}>
|
||||||
|
绑定
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
|
</Modal.Description>
|
||||||
|
</Modal.Content>
|
||||||
|
</Modal>
|
||||||
|
{
|
||||||
|
status.github_oauth && (
|
||||||
|
<Button onClick={()=>{onGitHubOAuthClicked(status.github_client_id)}}>绑定 GitHub 账号</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setShowEmailBindModal(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
绑定邮箱地址
|
||||||
|
</Button>
|
||||||
|
<Modal
|
||||||
|
onClose={() => setShowEmailBindModal(false)}
|
||||||
|
onOpen={() => setShowEmailBindModal(true)}
|
||||||
|
open={showEmailBindModal}
|
||||||
|
size={'tiny'}
|
||||||
|
style={{ maxWidth: '450px' }}
|
||||||
|
>
|
||||||
|
<Modal.Header>绑定邮箱地址</Modal.Header>
|
||||||
|
<Modal.Content>
|
||||||
|
<Modal.Description>
|
||||||
|
<Form size='large'>
|
||||||
|
<Form.Input
|
||||||
|
fluid
|
||||||
|
placeholder='输入邮箱地址'
|
||||||
|
onChange={handleInputChange}
|
||||||
|
name='email'
|
||||||
|
type='email'
|
||||||
|
action={
|
||||||
|
<Button onClick={sendVerificationCode} disabled={disableButton || loading}>
|
||||||
|
{disableButton ? `重新发送(${countdown})` : '获取验证码'}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Form.Input
|
||||||
|
fluid
|
||||||
|
placeholder='验证码'
|
||||||
|
name='email_verification_code'
|
||||||
|
value={inputs.email_verification_code}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
|
{turnstileEnabled ? (
|
||||||
|
<Turnstile
|
||||||
|
sitekey={turnstileSiteKey}
|
||||||
|
onVerify={(token) => {
|
||||||
|
setTurnstileToken(token);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', marginTop: '1rem' }}>
|
||||||
|
<Button
|
||||||
|
color=''
|
||||||
|
fluid
|
||||||
|
size='large'
|
||||||
|
onClick={bindEmail}
|
||||||
|
loading={loading}
|
||||||
|
>
|
||||||
|
确认绑定
|
||||||
|
</Button>
|
||||||
|
<div style={{ width: '1rem' }}></div>
|
||||||
|
<Button
|
||||||
|
fluid
|
||||||
|
size='large'
|
||||||
|
onClick={() => setShowEmailBindModal(false)}
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
</Modal.Description>
|
||||||
|
</Modal.Content>
|
||||||
|
</Modal>
|
||||||
|
<Modal
|
||||||
|
onClose={() => setShowAccountDeleteModal(false)}
|
||||||
|
onOpen={() => setShowAccountDeleteModal(true)}
|
||||||
|
open={showAccountDeleteModal}
|
||||||
|
size={'tiny'}
|
||||||
|
style={{ maxWidth: '450px' }}
|
||||||
|
>
|
||||||
|
<Modal.Header>危险操作</Modal.Header>
|
||||||
|
<Modal.Content>
|
||||||
|
<Message>您正在删除自己的帐户,将清空所有数据且不可恢复</Message>
|
||||||
|
<Modal.Description>
|
||||||
|
<Form size='large'>
|
||||||
|
<Form.Input
|
||||||
|
fluid
|
||||||
|
placeholder={`输入你的账户名 ${userState?.user?.username} 以确认删除`}
|
||||||
|
name='self_account_deletion_confirmation'
|
||||||
|
value={inputs.self_account_deletion_confirmation}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
|
{turnstileEnabled ? (
|
||||||
|
<Turnstile
|
||||||
|
sitekey={turnstileSiteKey}
|
||||||
|
onVerify={(token) => {
|
||||||
|
setTurnstileToken(token);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', marginTop: '1rem' }}>
|
||||||
|
<Button
|
||||||
|
color='red'
|
||||||
|
fluid
|
||||||
|
size='large'
|
||||||
|
onClick={deleteAccount}
|
||||||
|
loading={loading}
|
||||||
|
>
|
||||||
|
确认删除
|
||||||
|
</Button>
|
||||||
|
<div style={{ width: '1rem' }}></div>
|
||||||
|
<Button
|
||||||
|
fluid
|
||||||
|
size='large'
|
||||||
|
onClick={() => setShowAccountDeleteModal(false)}
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
</Modal.Description>
|
||||||
|
</Modal.Content>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PersonalSetting;
|
export default PersonalSetting;
|
||||||
|
20
web/src/components/utils.js
Normal file
20
web/src/components/utils.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { API, showError } from '../helpers';
|
||||||
|
|
||||||
|
export async function getOAuthState() {
|
||||||
|
const res = await API.get('/api/oauth/state');
|
||||||
|
const { success, message, data } = res.data;
|
||||||
|
if (success) {
|
||||||
|
return data;
|
||||||
|
} else {
|
||||||
|
showError(message);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function onGitHubOAuthClicked(github_client_id) {
|
||||||
|
const state = await getOAuthState();
|
||||||
|
if (!state) return;
|
||||||
|
window.open(
|
||||||
|
`https://github.com/login/oauth/authorize?client_id=${github_client_id}&state=${state}&scope=user:email`
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user