mirror of
				https://github.com/linux-do/new-api.git
				synced 2025-11-04 05:13:41 +08:00 
			
		
		
		
	Merge remote-tracking branch 'origin/main'
# Conflicts: # web/src/components/PersonalSetting.js
This commit is contained in:
		
							
								
								
									
										11
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								README.md
									
									
									
									
									
								
							@@ -86,10 +86,10 @@ _✨ 通过标准的 OpenAI API 格式访问所有的大模型,开箱即用 
 | 
			
		||||
   + [x] [360 智脑](https://ai.360.cn)
 | 
			
		||||
2. 支持配置镜像以及众多第三方代理服务:
 | 
			
		||||
   + [x] [OpenAI-SB](https://openai-sb.com)
 | 
			
		||||
   + [x] [CloseAI](https://console.closeai-asia.com/r/2412)
 | 
			
		||||
   + [x] [API2D](https://api2d.com/r/197971)
 | 
			
		||||
   + [x] [OhMyGPT](https://aigptx.top?aff=uFpUl2Kf)
 | 
			
		||||
   + [x] [AI Proxy](https://aiproxy.io/?i=OneAPI) (邀请码:`OneAPI`)
 | 
			
		||||
   + [x] [CloseAI](https://console.closeai-asia.com/r/2412)
 | 
			
		||||
   + [x] 自定义渠道:例如各种未收录的第三方代理服务
 | 
			
		||||
3. 支持通过**负载均衡**的方式访问多个渠道。
 | 
			
		||||
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`。
 | 
			
		||||
 | 
			
		||||
#### 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>
 | 
			
		||||
<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) {
 | 
			
		||||
	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")
 | 
			
		||||
	if username != nil {
 | 
			
		||||
		GitHubBind(c)
 | 
			
		||||
@@ -205,3 +213,22 @@ func GitHubBind(c *gin.Context) {
 | 
			
		||||
	})
 | 
			
		||||
	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")
 | 
			
		||||
 | 
			
		||||
		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)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,7 @@ func SetApiRouter(router *gin.Engine) {
 | 
			
		||||
		apiRouter.GET("/reset_password", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.SendPasswordResetEmail)
 | 
			
		||||
		apiRouter.POST("/user/reset", middleware.CriticalRateLimit(), controller.ResetPassword)
 | 
			
		||||
		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/bind", middleware.CriticalRateLimit(), middleware.UserAuth(), controller.WeChatBind)
 | 
			
		||||
		apiRouter.GET("/oauth/email/bind", middleware.CriticalRateLimit(), middleware.UserAuth(), controller.EmailBind)
 | 
			
		||||
 
 | 
			
		||||
@@ -13,8 +13,8 @@ const GitHubOAuth = () => {
 | 
			
		||||
 | 
			
		||||
  let navigate = useNavigate();
 | 
			
		||||
 | 
			
		||||
  const sendCode = async (code, count) => {
 | 
			
		||||
    const res = await API.get(`/api/oauth/github?code=${code}`);
 | 
			
		||||
  const sendCode = async (code, state, count) => {
 | 
			
		||||
    const res = await API.get(`/api/oauth/github?code=${code}&state=${state}`);
 | 
			
		||||
    const { success, message, data } = res.data;
 | 
			
		||||
    if (success) {
 | 
			
		||||
      if (message === 'bind') {
 | 
			
		||||
@@ -36,13 +36,14 @@ const GitHubOAuth = () => {
 | 
			
		||||
      count++;
 | 
			
		||||
      setPrompt(`出现错误,第 ${count} 次重试中...`);
 | 
			
		||||
      await new Promise((resolve) => setTimeout(resolve, count * 2000));
 | 
			
		||||
      await sendCode(code, count);
 | 
			
		||||
      await sendCode(code, state, count);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    let code = searchParams.get('code');
 | 
			
		||||
    sendCode(code, 0).then();
 | 
			
		||||
    let state = searchParams.get('state');
 | 
			
		||||
    sendCode(code, state, 0).then();
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  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 { UserContext } from '../context/User';
 | 
			
		||||
import { API, getLogo, showError, showSuccess } from '../helpers';
 | 
			
		||||
import { getOAuthState, onGitHubOAuthClicked } from './utils';
 | 
			
		||||
 | 
			
		||||
const LoginForm = () => {
 | 
			
		||||
  const [inputs, setInputs] = useState({
 | 
			
		||||
@@ -31,12 +32,6 @@ const LoginForm = () => {
 | 
			
		||||
 | 
			
		||||
  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 = () => {
 | 
			
		||||
    setShowWeChatLoginModal(true);
 | 
			
		||||
  };
 | 
			
		||||
@@ -131,7 +126,7 @@ const LoginForm = () => {
 | 
			
		||||
                circular
 | 
			
		||||
                color='black'
 | 
			
		||||
                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 Turnstile from 'react-turnstile';
 | 
			
		||||
import { UserContext } from '../context/User';
 | 
			
		||||
import { onGitHubOAuthClicked } from './utils';
 | 
			
		||||
 | 
			
		||||
const PersonalSetting = () => {
 | 
			
		||||
    const [userState, userDispatch] = useContext(UserContext);
 | 
			
		||||
    let navigate = useNavigate();
 | 
			
		||||
  const [userState, userDispatch] = useContext(UserContext);
 | 
			
		||||
  let navigate = useNavigate();
 | 
			
		||||
 | 
			
		||||
    const [inputs, setInputs] = useState({
 | 
			
		||||
        wechat_verification_code: '',
 | 
			
		||||
        email_verification_code: '',
 | 
			
		||||
        email: '',
 | 
			
		||||
        self_account_deletion_confirmation: ''
 | 
			
		||||
    });
 | 
			
		||||
    const [status, setStatus] = useState({});
 | 
			
		||||
    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 [affLink, setAffLink] = useState("");
 | 
			
		||||
    const [systemToken, setSystemToken] = useState("");
 | 
			
		||||
  const [inputs, setInputs] = useState({
 | 
			
		||||
    wechat_verification_code: '',
 | 
			
		||||
    email_verification_code: '',
 | 
			
		||||
    email: '',
 | 
			
		||||
    self_account_deletion_confirmation: ''
 | 
			
		||||
  });
 | 
			
		||||
  const [status, setStatus] = useState({});
 | 
			
		||||
  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 [affLink, setAffLink] = useState("");
 | 
			
		||||
  const [systemToken, setSystemToken] = useState("");
 | 
			
		||||
 | 
			
		||||
    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);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }, []);
 | 
			
		||||
  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);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
    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(() => {
 | 
			
		||||
    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]);
 | 
			
		||||
 | 
			
		||||
    const handleInputChange = (e, { name, value }) => {
 | 
			
		||||
        setInputs((inputs) => ({ ...inputs, [name]: value }));
 | 
			
		||||
    };
 | 
			
		||||
  const handleInputChange = (e, { 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);
 | 
			
		||||
            setAffLink("");
 | 
			
		||||
            await copy(data);
 | 
			
		||||
            showSuccess(`令牌已重置并已复制到剪贴板`);
 | 
			
		||||
        } else {
 | 
			
		||||
            showError(message);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
  const generateAccessToken = async () => {
 | 
			
		||||
    const res = await API.get('/api/user/token');
 | 
			
		||||
    const { success, message, data } = res.data;
 | 
			
		||||
    if (success) {
 | 
			
		||||
      setSystemToken(data);
 | 
			
		||||
      setAffLink(""); 
 | 
			
		||||
      await copy(data);
 | 
			
		||||
      showSuccess(`令牌已重置并已复制到剪贴板`);
 | 
			
		||||
    } else {
 | 
			
		||||
      showError(message);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
    const getAffLink = async () => {
 | 
			
		||||
        const res = await API.get('/api/user/aff');
 | 
			
		||||
        const { success, message, data } = res.data;
 | 
			
		||||
        if (success) {
 | 
			
		||||
            let link = `${window.location.origin}/register?aff=${data}`;
 | 
			
		||||
            setAffLink(link);
 | 
			
		||||
            setSystemToken("");
 | 
			
		||||
            await copy(link);
 | 
			
		||||
            showSuccess(`邀请链接已复制到剪切板`);
 | 
			
		||||
        } else {
 | 
			
		||||
            showError(message);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
  const getAffLink = async () => {
 | 
			
		||||
    const res = await API.get('/api/user/aff');
 | 
			
		||||
    const { success, message, data } = res.data;
 | 
			
		||||
    if (success) {
 | 
			
		||||
      let link = `${window.location.origin}/register?aff=${data}`;
 | 
			
		||||
      setAffLink(link);
 | 
			
		||||
      setSystemToken("");
 | 
			
		||||
      await copy(link);
 | 
			
		||||
      showSuccess(`邀请链接已复制到剪切板`);
 | 
			
		||||
    } else {
 | 
			
		||||
      showError(message);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
    const handleAffLinkClick = async (e) => {
 | 
			
		||||
        e.target.select();
 | 
			
		||||
        await copy(e.target.value);
 | 
			
		||||
        showSuccess(`邀请链接已复制到剪切板`);
 | 
			
		||||
    };
 | 
			
		||||
  const handleAffLinkClick = async (e) => {
 | 
			
		||||
    e.target.select();
 | 
			
		||||
    await copy(e.target.value);
 | 
			
		||||
    showSuccess(`邀请链接已复制到剪切板`);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
    const handleSystemTokenClick = async (e) => {
 | 
			
		||||
        e.target.select();
 | 
			
		||||
        await copy(e.target.value);
 | 
			
		||||
        showSuccess(`系统令牌已复制到剪切板`);
 | 
			
		||||
    };
 | 
			
		||||
  const handleSystemTokenClick = async (e) => {
 | 
			
		||||
    e.target.select();
 | 
			
		||||
    await copy(e.target.value);
 | 
			
		||||
    showSuccess(`系统令牌已复制到剪切板`);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
    const deleteAccount = async () => {
 | 
			
		||||
        if (inputs.self_account_deletion_confirmation !== userState.user.username) {
 | 
			
		||||
            showError('请输入你的账户名以确认删除!');
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
  const deleteAccount = async () => {
 | 
			
		||||
    if (inputs.self_account_deletion_confirmation !== userState.user.username) {
 | 
			
		||||
      showError('请输入你的账户名以确认删除!');
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        const res = await API.delete('/api/user/self');
 | 
			
		||||
        const { success, message } = res.data;
 | 
			
		||||
    const res = await API.delete('/api/user/self');
 | 
			
		||||
    const { success, message } = res.data;
 | 
			
		||||
 | 
			
		||||
        if (success) {
 | 
			
		||||
            showSuccess('账户已删除!');
 | 
			
		||||
            await API.get('/api/user/logout');
 | 
			
		||||
            userDispatch({ type: 'logout' });
 | 
			
		||||
            localStorage.removeItem('user');
 | 
			
		||||
            navigate('/login');
 | 
			
		||||
        } else {
 | 
			
		||||
            showError(message);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    if (success) {
 | 
			
		||||
      showSuccess('账户已删除!');
 | 
			
		||||
      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('微信账户绑定成功!');
 | 
			
		||||
            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 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('微信账户绑定成功!');
 | 
			
		||||
      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`
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user