From 9ab4f7a2715a3167c712795fc468f4368b3b2351 Mon Sep 17 00:00:00 2001 From: CaIon <1808837298@qq.com> Date: Sat, 9 Sep 2023 03:11:42 +0800 Subject: [PATCH] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E4=B8=8D=E5=BF=85=E8=A6=81?= =?UTF-8?q?=E7=9A=84=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/constants.go | 8 +- controller/midjourney.go | 140 ++-- controller/misc.go | 7 +- controller/relay-mj.go | 1 + controller/relay-text.go | 6 - controller/topup.go | 95 +-- middleware/distributor.go | 52 +- model/midjourney.go | 1 + model/option.go | 23 +- web/src/components/PersonalSetting.js | 387 ++++----- web/src/components/SystemSetting.js | 1109 +++++++++++++------------ web/src/pages/Home/index.js | 98 ++- web/src/pages/TopUp/index.js | 32 +- 13 files changed, 943 insertions(+), 1016 deletions(-) diff --git a/common/constants.go b/common/constants.go index 8e834a25..b908a560 100644 --- a/common/constants.go +++ b/common/constants.go @@ -13,6 +13,10 @@ var StartTime = time.Now().Unix() // unit: second var Version = "v0.0.0" // this hard coding will be replaced automatically when building, no need to manually change var SystemName = "One API" var ServerAddress = "http://localhost:3000" +var PayAddress = "" +var EpayId = "" +var EpayKey = "" +var Price = 7 var Footer = "" var Logo = "" var TopUpLink = "" @@ -94,10 +98,6 @@ var RequestInterval = time.Duration(requestInterval) * time.Second var SyncFrequency = 10 * 60 // unit is second, will be overwritten by SYNC_FREQUENCY -var NormalPrice = 1.5 -var StablePrice = 6.0 -var BasePrice = 1.5 - const ( RoleGuestUser = 0 RoleCommonUser = 1 diff --git a/controller/midjourney.go b/controller/midjourney.go index e9674efc..6f103363 100644 --- a/controller/midjourney.go +++ b/controller/midjourney.go @@ -19,90 +19,84 @@ func UpdateMidjourneyTask() { for { defer func() { if err := recover(); err != nil { - log.Printf("UpdateMidjourneyTask: %v", err) + log.Printf("UpdateMidjourneyTask panic: %v", err) } }() time.Sleep(time.Duration(15) * time.Second) tasks := model.GetAllUnFinishTasks() if len(tasks) != 0 { - //log.Printf("UpdateMidjourneyTask: %v", time.Now()) - ids := make([]string, 0) for _, task := range tasks { - ids = append(ids, task.MjId) - } - requestUrl := "http://107.173.171.147:8080/mj/task/list-by-condition" - requestBody := map[string]interface{}{ - "ids": ids, - } - jsonStr, err := json.Marshal(requestBody) - if err != nil { - log.Printf("UpdateMidjourneyTask: %v", err) - continue - } - req, err := http.NewRequest("POST", requestUrl, bytes.NewBuffer(jsonStr)) - if err != nil { - log.Printf("UpdateMidjourneyTask: %v", err) - continue - } - req.Header.Set("Content-Type", "application/json") - req.Header.Set("mj-api-secret", "uhiftyuwadbkjshbiklahcuitguasguzhxliawodawdu") - resp, err := httpClient.Do(req) - if err != nil { - log.Printf("UpdateMidjourneyTask: %v", err) - continue - } - defer resp.Body.Close() - var response []Midjourney - err = json.NewDecoder(resp.Body).Decode(&response) - if err != nil { - log.Printf("UpdateMidjourneyTask: %v", err) - continue - } - for _, responseItem := range response { - var midjourneyTask *model.Midjourney - for _, mj := range tasks { - mj.MjId = responseItem.MjId - midjourneyTask = model.GetMjByuId(mj.Id) + midjourneyChannel, err := model.GetChannelById(task.ChannelId, true) + if err != nil { + log.Printf("UpdateMidjourneyTask: %v", err) + task.FailReason = fmt.Sprintf("获取渠道信息失败,请联系管理员,渠道ID:%d", task.ChannelId) + task.Status = "FAILURE" + task.Progress = "100%" + err := task.Update() + if err != nil { + log.Printf("UpdateMidjourneyTask error: %v", err) + } + continue } - if midjourneyTask != nil { - midjourneyTask.Code = 1 - midjourneyTask.Progress = responseItem.Progress - midjourneyTask.PromptEn = responseItem.PromptEn - midjourneyTask.State = responseItem.State - midjourneyTask.SubmitTime = responseItem.SubmitTime - midjourneyTask.StartTime = responseItem.StartTime - midjourneyTask.FinishTime = responseItem.FinishTime - midjourneyTask.ImageUrl = responseItem.ImageUrl - midjourneyTask.Status = responseItem.Status - midjourneyTask.FailReason = responseItem.FailReason - if midjourneyTask.Progress != "100%" && responseItem.FailReason != "" { - log.Println(midjourneyTask.MjId + " 构建失败," + midjourneyTask.FailReason) - midjourneyTask.Progress = "100%" - err = model.CacheUpdateUserQuota(midjourneyTask.UserId) - if err != nil { - log.Println("error update user quota cache: " + err.Error()) - } else { - modelRatio := common.GetModelRatio(imageModel) - groupRatio := common.GetGroupRatio("default") - ratio := modelRatio * groupRatio - quota := int(ratio * 1 * 1000) - if quota != 0 { - err := model.IncreaseUserQuota(midjourneyTask.UserId, quota) - if err != nil { - log.Println("fail to increase user quota") - } - logContent := fmt.Sprintf("%s 构图失败,补偿 %s", midjourneyTask.MjId, common.LogQuota(quota)) - model.RecordLog(midjourneyTask.UserId, 1, logContent) + requestUrl := fmt.Sprintf("%s/mj/task/%s/fetch", midjourneyChannel.BaseURL, task.MjId) + + req, err := http.NewRequest("GET", requestUrl, bytes.NewBuffer([]byte(""))) + if err != nil { + log.Printf("UpdateMidjourneyTask error: %v", err) + continue + } + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("mj-api-secret", midjourneyChannel.Key) + resp, err := httpClient.Do(req) + if err != nil { + log.Printf("UpdateMidjourneyTask error: %v", err) + continue + } + defer resp.Body.Close() + var responseItem Midjourney + err = json.NewDecoder(resp.Body).Decode(&responseItem) + if err != nil { + log.Printf("UpdateMidjourneyTask error: %v", err) + continue + } + task.Code = 1 + task.Progress = responseItem.Progress + task.PromptEn = responseItem.PromptEn + task.State = responseItem.State + task.SubmitTime = responseItem.SubmitTime + task.StartTime = responseItem.StartTime + task.FinishTime = responseItem.FinishTime + task.ImageUrl = responseItem.ImageUrl + task.Status = responseItem.Status + task.FailReason = responseItem.FailReason + if task.Progress != "100%" && responseItem.FailReason != "" { + log.Println(task.MjId + " 构建失败," + task.FailReason) + task.Progress = "100%" + err = model.CacheUpdateUserQuota(task.UserId) + if err != nil { + log.Println("error update user quota cache: " + err.Error()) + } else { + modelRatio := common.GetModelRatio(imageModel) + groupRatio := common.GetGroupRatio("default") + ratio := modelRatio * groupRatio + quota := int(ratio * 1 * 1000) + if quota != 0 { + err := model.IncreaseUserQuota(task.UserId, quota) + if err != nil { + log.Println("fail to increase user quota") } + logContent := fmt.Sprintf("%s 构图失败,补偿 %s", task.MjId, common.LogQuota(quota)) + model.RecordLog(task.UserId, 1, logContent) } } - - err = midjourneyTask.Update() - if err != nil { - log.Printf("UpdateMidjourneyTaskFail: %v", err) - } - log.Printf("UpdateMidjourneyTask: %v", midjourneyTask) } + + err = task.Update() + if err != nil { + log.Printf("UpdateMidjourneyTask error: %v", err) + } + log.Printf("UpdateMidjourneyTask success: %v", task) } } } diff --git a/controller/misc.go b/controller/misc.go index e382e482..cf324b78 100644 --- a/controller/misc.go +++ b/controller/misc.go @@ -27,15 +27,16 @@ func GetStatus(c *gin.Context) { "wechat_qrcode": common.WeChatAccountQRCodeImageURL, "wechat_login": common.WeChatAuthEnabled, "server_address": common.ServerAddress, + "pay_address": common.PayAddress, + "epay_id": common.EpayId, + "epay_key": common.EpayKey, + "price": common.Price, "turnstile_check": common.TurnstileCheckEnabled, "turnstile_site_key": common.TurnstileSiteKey, "top_up_link": common.TopUpLink, "chat_link": common.ChatLink, "quota_per_unit": common.QuotaPerUnit, "display_in_currency": common.DisplayInCurrencyEnabled, - "normal_price": common.NormalPrice, - "stable_price": common.StablePrice, - "base_price": common.BasePrice, }, }) return diff --git a/controller/relay-mj.go b/controller/relay-mj.go index e1f6a2cd..3b0f762a 100644 --- a/controller/relay-mj.go +++ b/controller/relay-mj.go @@ -356,6 +356,7 @@ func relayMidjourneySubmit(c *gin.Context, relayMode int) *MidjourneyResponse { Status: "", Progress: "0%", FailReason: "", + ChannelId: c.GetInt("channel_id"), } if midjResponse.Code == 4 || midjResponse.Code == 24 { midjourneyTask.FailReason = midjResponse.Description diff --git a/controller/relay-text.go b/controller/relay-text.go index 20b1696a..814f480c 100644 --- a/controller/relay-text.go +++ b/controller/relay-text.go @@ -105,7 +105,6 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { case common.ChannelTypeXunfei: apiType = APITypeXunfei } - isStable := c.GetBool("stable") baseURL := common.ChannelBaseURLs[channelType] requestURL := c.Request.URL.String() @@ -189,15 +188,10 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { preConsumedTokens = promptTokens + textRequest.MaxTokens } modelRatio := common.GetModelRatio(textRequest.Model) - stableRatio := modelRatio groupRatio := common.GetGroupRatio(group) ratio := modelRatio * groupRatio preConsumedQuota := int(float64(preConsumedTokens) * ratio) userQuota, err := model.CacheGetUserQuota(userId) - if isStable { - stableRatio = (common.StablePrice / common.BasePrice) * modelRatio - ratio = stableRatio * groupRatio - } if err != nil { return errorWrapper(err, "get_user_quota_failed", http.StatusInternalServerError) } diff --git a/controller/topup.go b/controller/topup.go index 81b4c19d..1a83e79f 100644 --- a/controller/topup.go +++ b/controller/topup.go @@ -24,31 +24,23 @@ type AmountRequest struct { TopUpCode string `json:"top_up_code"` } -//var client, _ = epay.NewClientWithUrl(&epay.Config{ -// PartnerID: "1096", -// Key: "n08V9LpE8JffA3NPP893689u8p39NV9J", -//}, "https://api.lempay.org") - -var client, _ = epay.NewClientWithUrl(&epay.Config{ - PartnerID: "1064", - Key: "nqrrZ5RjR86mKP8rKkyrOY5Pg8NmYfKR", -}, "https://pay.yunjuw.cn") - -func GetAmount(id int, count float64, topUpCode string) float64 { - amount := count * 1.5 - if topUpCode != "" { - if topUpCode == "nekoapi" { - if id == 89 { - amount = count * 0.8 - } else if id == 105 || id == 107 { - amount = count * 1.2 - } else if id == 1 { - amount = count * 1 - } else if id == 98 { - amount = count * 1.1 - } - } +func GetEpayClient() *epay.Client { + if common.PayAddress == "" || common.EpayId == "" || common.EpayKey == "" { + return nil } + withUrl, err := epay.NewClientWithUrl(&epay.Config{ + PartnerID: common.EpayId, + Key: common.EpayKey, + }, common.PayAddress) + if err != nil { + return nil + } + return withUrl +} + +func GetAmount(count float64) float64 { + // 别问为什么用float64,问就是这么点钱没必要 + amount := count * float64(common.Price) return amount } @@ -60,38 +52,24 @@ func RequestEpay(c *gin.Context) { return } id := c.GetInt("id") - amount := GetAmount(id, float64(req.Amount), req.TopUpCode) - if id != 1 { - if req.Amount < 10 { - c.JSON(200, gin.H{"message": "最小充值10元", "data": amount, "count": 10}) - return - } - } + amount := GetAmount(float64(req.Amount)) + if req.PaymentMethod == "zfb" { - if amount > 2000 { - c.JSON(200, gin.H{"message": "支付宝最大充值2000元", "data": amount, "count": 2000}) - return - } req.PaymentMethod = "alipay" } if req.PaymentMethod == "wx" { - if amount > 2000 { - c.JSON(200, gin.H{"message": "微信最大充值2000元", "data": amount, "count": 2000}) - return - } req.PaymentMethod = "wxpay" } - returnUrl, _ := url.Parse("https://nekoapi.com/log") - notifyUrl, _ := url.Parse("https://nekoapi.com/api/user/epay/notify") + returnUrl, _ := url.Parse(common.ServerAddress + "/log") + notifyUrl, _ := url.Parse(common.ServerAddress + "/api/user/epay/notify") tradeNo := strconv.FormatInt(time.Now().Unix(), 10) payMoney := amount - //if payMoney < 400 { - // payMoney = amount * 0.99 - // if amount-payMoney > 2 { - // payMoney = amount - 2 - // } - //} + client := GetEpayClient() + if client == nil { + c.JSON(200, gin.H{"message": "error", "data": "当前管理员未配置支付信息"}) + return + } uri, params, err := client.Purchase(&epay.PurchaseArgs{ Type: epay.PurchaseType(req.PaymentMethod), ServiceTradeNo: "A" + tradeNo, @@ -126,6 +104,14 @@ func EpayNotify(c *gin.Context) { r[t] = c.Request.URL.Query().Get(t) return r }, map[string]string{}) + client := GetEpayClient() + if client == nil { + log.Println("易支付回调失败 未找到配置信息") + _, err := c.Writer.Write([]byte("fail")) + if err != nil { + log.Println("易支付回调写入失败") + } + } verifyInfo, err := client.Verify(params) if err == nil && verifyInfo.VerifyStatus { _, err := c.Writer.Write([]byte("success")) @@ -168,20 +154,9 @@ func RequestAmount(c *gin.Context) { var req AmountRequest err := c.ShouldBindJSON(&req) if err != nil { - c.JSON(200, gin.H{"message": err.Error(), "data": 10}) + c.JSON(200, gin.H{"message": "error", "data": "参数错误"}) return } - id := c.GetInt("id") - if id != 1 { - if req.Amount < 10 { - c.JSON(200, gin.H{"message": "最小充值10刀", "data": GetAmount(id, 10, req.TopUpCode), "count": 10}) - return - } - //if req.Amount > 1500 { - // c.JSON(200, gin.H{"message": "最大充值1000刀", "data": GetAmount(id, 1000, req.TopUpCode), "count": 1500}) - // return - //} - } - c.JSON(200, gin.H{"message": "success", "data": GetAmount(id, float64(req.Amount), req.TopUpCode)}) + c.JSON(200, gin.H{"message": "success", "data": GetAmount(float64(req.Amount))}) } diff --git a/middleware/distributor.go b/middleware/distributor.go index 3e664cee..c4ee6e47 100644 --- a/middleware/distributor.go +++ b/middleware/distributor.go @@ -95,51 +95,21 @@ func Distribute() func(c *gin.Context) { modelRequest.Model = "dall-e" } } - isStable := false channel, err = model.CacheGetRandomSatisfiedChannel(userGroup, modelRequest.Model) - c.Set("stable", false) if err != nil { message := fmt.Sprintf("当前分组 %s 下对于模型 %s 无可用渠道", userGroup, modelRequest.Model) - if strings.HasPrefix(modelRequest.Model, "gpt-4") { - common.SysLog("GPT-4低价渠道宕机,正在尝试转换") - nowUser, err := model.GetUserById(userId, false) - if err == nil { - if nowUser.StableMode { - userGroup = "svip" - //stableRatio = (common.StablePrice / common.BasePrice) * modelRatio - userMaxPrice, _ := strconv.ParseFloat(nowUser.MaxPrice, 64) - if userMaxPrice < common.StablePrice { - message = "当前低价通道不可用,稳定渠道价格为" + strconv.FormatFloat(common.StablePrice, 'f', -1, 64) + "R/刀" - } else { - //common.SysLog(fmt.Sprintf("用户 %s 使用稳定渠道", nowUser.Username)) - channel, err = model.CacheGetRandomSatisfiedChannel(userGroup, modelRequest.Model) - if err != nil { - message = "稳定渠道已经宕机,请联系管理员" - } - isStable = true - common.SysLog(fmt.Sprintf("用户 %s 使用稳定渠道 %v", nowUser.Username, channel)) - c.Set("stable", true) - } - - } else { - message = "当前低价通道不可用,请稍后再试,或者在后台开启稳定渠道模式" - } - } - } - //if channel == nil { - // common.SysError(fmt.Sprintf("渠道不存在:%d", channel.Id)) - // message = "数据库一致性已被破坏,请联系管理员" - //} - if !isStable { - c.JSON(http.StatusInternalServerError, gin.H{ - "error": gin.H{ - "message": message, - "type": "one_api_error", - }, - }) - c.Abort() - return + if channel != nil { + common.SysError(fmt.Sprintf("渠道不存在:%d", channel.Id)) + message = "数据库一致性已被破坏,请联系管理员" } + c.JSON(http.StatusServiceUnavailable, gin.H{ + "error": gin.H{ + "message": message, + "type": "one_api_error", + }, + }) + c.Abort() + return } } c.Set("channel", channel.Type) diff --git a/model/midjourney.go b/model/midjourney.go index bb723e8c..64b99349 100644 --- a/model/midjourney.go +++ b/model/midjourney.go @@ -17,6 +17,7 @@ type Midjourney struct { Status string `json:"status"` Progress string `json:"progress"` FailReason string `json:"fail_reason"` + ChannelId int `json:"channel_id"` } func GetAllUserTask(userId int, startIdx int, num int) []*Midjourney { diff --git a/model/option.go b/model/option.go index 604e19e5..905ef3d5 100644 --- a/model/option.go +++ b/model/option.go @@ -53,6 +53,10 @@ func InitOptionMap() { common.OptionMap["SystemName"] = common.SystemName common.OptionMap["Logo"] = common.Logo common.OptionMap["ServerAddress"] = "" + common.OptionMap["PayAddress"] = "" + common.OptionMap["EpayId"] = "" + common.OptionMap["EpayKey"] = "" + common.OptionMap["Price"] = strconv.Itoa(common.Price) common.OptionMap["GitHubClientId"] = "" common.OptionMap["GitHubClientSecret"] = "" common.OptionMap["WeChatServerAddress"] = "" @@ -71,9 +75,6 @@ func InitOptionMap() { common.OptionMap["ChatLink"] = common.ChatLink common.OptionMap["QuotaPerUnit"] = strconv.FormatFloat(common.QuotaPerUnit, 'f', -1, 64) common.OptionMap["RetryTimes"] = strconv.Itoa(common.RetryTimes) - common.OptionMap["NormalPrice"] = strconv.FormatFloat(common.NormalPrice, 'f', -1, 64) - common.OptionMap["StablePrice"] = strconv.FormatFloat(common.StablePrice, 'f', -1, 64) - common.OptionMap["BasePrice"] = strconv.FormatFloat(common.BasePrice, 'f', -1, 64) common.OptionMapRWMutex.Unlock() loadOptionsFromDatabase() @@ -157,8 +158,6 @@ func updateOptionMap(key string, value string) (err error) { common.LogConsumeEnabled = boolValue case "DisplayInCurrencyEnabled": common.DisplayInCurrencyEnabled = boolValue - case "DisplayTokenStatEnabled": - common.DisplayTokenStatEnabled = boolValue } } switch key { @@ -177,6 +176,14 @@ func updateOptionMap(key string, value string) (err error) { common.SMTPToken = value case "ServerAddress": common.ServerAddress = value + case "PayAddress": + common.PayAddress = value + case "EpayId": + common.EpayId = value + case "EpayKey": + common.EpayKey = value + case "Price": + common.Price, _ = strconv.Atoi(value) case "GitHubClientId": common.GitHubClientId = value case "GitHubClientSecret": @@ -217,12 +224,6 @@ func updateOptionMap(key string, value string) (err error) { common.TopUpLink = value case "ChatLink": common.ChatLink = value - case "NormalPrice": - common.NormalPrice, _ = strconv.ParseFloat(value, 64) - case "BasePrice": - common.BasePrice, _ = strconv.ParseFloat(value, 64) - case "StablePrice": - common.StablePrice, _ = strconv.ParseFloat(value, 64) case "ChannelDisableThreshold": common.ChannelDisableThreshold, _ = strconv.ParseFloat(value, 64) case "QuotaPerUnit": diff --git a/web/src/components/PersonalSetting.js b/web/src/components/PersonalSetting.js index 11873a30..7cd40cf2 100644 --- a/web/src/components/PersonalSetting.js +++ b/web/src/components/PersonalSetting.js @@ -1,13 +1,13 @@ import React, { useContext, useEffect, useState } from 'react'; -import {Button, Input, Checkbox, Divider, Form, Header, Image, Message, Modal} from 'semantic-ui-react'; +import { Button, Divider, Form, Header, Image, Message, Modal } from 'semantic-ui-react'; 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'; 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: '', @@ -15,10 +15,6 @@ const PersonalSetting = () => { email: '', self_account_deletion_confirmation: '' }); - const [stableMode, setStableMode] = useState({ - stableMode: false, - maxPrice: 7, - }); const [status, setStatus] = useState({}); const [showWeChatBindModal, setShowWeChatBindModal] = useState(false); const [showEmailBindModal, setShowEmailBindModal] = useState(false); @@ -29,12 +25,8 @@ const PersonalSetting = () => { const [loading, setLoading] = useState(false); const [disableButton, setDisableButton] = useState(false); const [countdown, setCountdown] = useState(30); - const [affLink, setAffLink] = useState(""); - const [systemToken, setSystemToken] = useState(""); - - // setStableMode(userState.user.stableMode, userState.user.maxPrice); - console.log(userState.user) - + const [affLink, setAffLink] = useState(""); + const [systemToken, setSystemToken] = useState(""); useEffect(() => { let status = localStorage.getItem('status'); @@ -46,9 +38,6 @@ const PersonalSetting = () => { setTurnstileSiteKey(status.turnstile_site_key); } } - // if (userState.user !== undefined) { - // setStableMode(userState.user.stable_mode, userState.user.max_price); - // } }, []); useEffect(() => { @@ -64,30 +53,17 @@ const PersonalSetting = () => { return () => clearInterval(countdownInterval); // Clean up on unmount }, [disableButton, countdown]); - useEffect(() => { - if (userState.user !== undefined) { - setStableMode({ - stableMode: userState.user.stable_mode, - maxPrice: userState.user.max_price - }) - // if (stableMode.localMaxPrice !== userState.user.max_price) { - // setStableMode({ - // localMaxPrice: userState.user.max_price - // }) - // } - } - }, [userState]); - - 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; + const { success, message, data } = res.data; if (success) { setSystemToken(data); - setAffLink("");await copy(data); + setAffLink(""); + await copy(data); showSuccess(`令牌已重置并已复制到剪贴板`); } else { showError(message); @@ -96,11 +72,12 @@ const PersonalSetting = () => { const getAffLink = async () => { const res = await API.get('/api/user/aff'); - const {success, message, data} = res.data; + const { success, message, data } = res.data; if (success) { let link = `${window.location.origin}/register?aff=${data}`; setAffLink(link); - setSystemToken("");await copy(link); + setSystemToken(""); + await copy(link); showSuccess(`邀请链接已复制到剪切板`); } else { showError(message); @@ -108,95 +85,95 @@ const PersonalSetting = () => { }; const handleAffLinkClick = async (e) => { - e.target.select(); - await copy(e.target.value); - showSuccess(`邀请链接已复制到剪切板`); - }; + 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 () => { + 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 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 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 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 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); - }; - - // const setStableMod = ; + 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 ( -
+
通用设置
注意,此处生成的令牌用于系统管理,而非用于请求 OpenAI 相关的服务,请知悉。 @@ -209,84 +186,26 @@ const PersonalSetting = () => { - -
GPT-4消费设置
-
- - { - setStableMode({ - ...stableMode, - stableMode: data.checked - }) - } - } - > - - { - setStableMode({ - ...stableMode, - maxPrice: data.value - }) - } - } - > - {/**/} - {/**/} - - -
- {/* {*/} - {/* // if (inputs.email_verification_code === '') return;*/} - {/* console.log(data)*/} - {/* }*/} - {/*}>*/} - {/**/} + {systemToken && ( - - )} - {affLink && ( - - )} + + )} + {affLink && ( + + )} +
账号绑定
{ status.wechat_login && ( @@ -307,8 +226,8 @@ const PersonalSetting = () => { > - -
+ +

微信扫码关注公众号,输入「验证码」获取验证码(三分钟内有效)

@@ -345,7 +264,7 @@ const PersonalSetting = () => { onOpen={() => setShowEmailBindModal(true)} open={showEmailBindModal} size={'tiny'} - style={{maxWidth: '450px'}} + style={{ maxWidth: '450px' }} > 绑定邮箱地址 @@ -381,25 +300,25 @@ const PersonalSetting = () => { <> )}
- -
- -
- + +
+ +
+ @@ -408,11 +327,11 @@ const PersonalSetting = () => { onOpen={() => setShowAccountDeleteModal(true)} open={showAccountDeleteModal} size={'tiny'} - style={{maxWidth: '450px'}} + style={{ maxWidth: '450px' }} > 危险操作 - - 您正在删除自己的帐户,将清空所有数据且不可恢复 + + 您正在删除自己的帐户,将清空所有数据且不可恢复
{ /> ) : ( <> - )}
-
- -
+ )} +
+ +
+ +
@@ -457,4 +378,4 @@ const PersonalSetting = () => { ); }; -export default PersonalSetting; +export default PersonalSetting; \ No newline at end of file diff --git a/web/src/components/SystemSetting.js b/web/src/components/SystemSetting.js index 7b34ce5b..250b9bad 100644 --- a/web/src/components/SystemSetting.js +++ b/web/src/components/SystemSetting.js @@ -1,537 +1,594 @@ -import React, { useEffect, useState } from 'react'; -import { Button, Divider, Form, Grid, Header, Modal, Message } from 'semantic-ui-react'; -import { API, removeTrailingSlash, showError } from '../helpers'; +import React, {useEffect, useState} from 'react'; +import {Button, Divider, Form, Grid, Header, Modal, Message} from 'semantic-ui-react'; +import {API, removeTrailingSlash, showError} from '../helpers'; const SystemSetting = () => { - let [inputs, setInputs] = useState({ - PasswordLoginEnabled: '', - PasswordRegisterEnabled: '', - EmailVerificationEnabled: '', - GitHubOAuthEnabled: '', - GitHubClientId: '', - GitHubClientSecret: '', - Notice: '', - SMTPServer: '', - SMTPPort: '', - SMTPAccount: '', - SMTPFrom: '', - SMTPToken: '', - ServerAddress: '', - Footer: '', - WeChatAuthEnabled: '', - WeChatServerAddress: '', - WeChatServerToken: '', - WeChatAccountQRCodeImageURL: '', - TurnstileCheckEnabled: '', - TurnstileSiteKey: '', - TurnstileSecretKey: '', - RegisterEnabled: '', - EmailDomainRestrictionEnabled: '', - EmailDomainWhitelist: '' - }); - const [originInputs, setOriginInputs] = useState({}); - let [loading, setLoading] = useState(false); - const [EmailDomainWhitelist, setEmailDomainWhitelist] = useState([]); - const [restrictedDomainInput, setRestrictedDomainInput] = useState(''); - const [showPasswordWarningModal, setShowPasswordWarningModal] = useState(false); - - const getOptions = async () => { - const res = await API.get('/api/option/'); - const { success, message, data } = res.data; - if (success) { - let newInputs = {}; - data.forEach((item) => { - newInputs[item.key] = item.value; - }); - setInputs({ - ...newInputs, - EmailDomainWhitelist: newInputs.EmailDomainWhitelist.split(',') - }); - setOriginInputs(newInputs); - - setEmailDomainWhitelist(newInputs.EmailDomainWhitelist.split(',').map((item) => { - return { key: item, text: item, value: item }; - })); - } else { - showError(message); - } - }; - - useEffect(() => { - getOptions().then(); - }, []); - - const updateOption = async (key, value) => { - setLoading(true); - switch (key) { - case 'PasswordLoginEnabled': - case 'PasswordRegisterEnabled': - case 'EmailVerificationEnabled': - case 'GitHubOAuthEnabled': - case 'WeChatAuthEnabled': - case 'TurnstileCheckEnabled': - case 'EmailDomainRestrictionEnabled': - case 'RegisterEnabled': - value = inputs[key] === 'true' ? 'false' : 'true'; - break; - default: - break; - } - const res = await API.put('/api/option/', { - key, - value + let [inputs, setInputs] = useState({ + PasswordLoginEnabled: '', + PasswordRegisterEnabled: '', + EmailVerificationEnabled: '', + GitHubOAuthEnabled: '', + GitHubClientId: '', + GitHubClientSecret: '', + Notice: '', + SMTPServer: '', + SMTPPort: '', + SMTPAccount: '', + SMTPFrom: '', + SMTPToken: '', + ServerAddress: '', + EpayId: '', + EpayKey: '', + Price: '', + PayAddress: '', + Footer: '', + WeChatAuthEnabled: '', + WeChatServerAddress: '', + WeChatServerToken: '', + WeChatAccountQRCodeImageURL: '', + TurnstileCheckEnabled: '', + TurnstileSiteKey: '', + TurnstileSecretKey: '', + RegisterEnabled: '', + EmailDomainRestrictionEnabled: '', + EmailDomainWhitelist: '' }); - const { success, message } = res.data; - if (success) { - if (key === 'EmailDomainWhitelist') { - value = value.split(','); - } - setInputs((inputs) => ({ - ...inputs, [key]: value - })); - } else { - showError(message); - } - setLoading(false); - }; + const [originInputs, setOriginInputs] = useState({}); + let [loading, setLoading] = useState(false); + const [EmailDomainWhitelist, setEmailDomainWhitelist] = useState([]); + const [restrictedDomainInput, setRestrictedDomainInput] = useState(''); + const [showPasswordWarningModal, setShowPasswordWarningModal] = useState(false); - const handleInputChange = async (e, { name, value }) => { - if (name === 'PasswordLoginEnabled' && inputs[name] === 'true') { - // block disabling password login - setShowPasswordWarningModal(true); - return; - } - if ( - name === 'Notice' || - name.startsWith('SMTP') || - name === 'ServerAddress' || - name === 'GitHubClientId' || - name === 'GitHubClientSecret' || - name === 'WeChatServerAddress' || - name === 'WeChatServerToken' || - name === 'WeChatAccountQRCodeImageURL' || - name === 'TurnstileSiteKey' || - name === 'TurnstileSecretKey' || - name === 'EmailDomainWhitelist' - ) { - setInputs((inputs) => ({ ...inputs, [name]: value })); - } else { - await updateOption(name, value); - } - }; + const getOptions = async () => { + const res = await API.get('/api/option/'); + const {success, message, data} = res.data; + if (success) { + let newInputs = {}; + data.forEach((item) => { + newInputs[item.key] = item.value; + }); + setInputs({ + ...newInputs, + EmailDomainWhitelist: newInputs.EmailDomainWhitelist.split(',') + }); + setOriginInputs(newInputs); - const submitServerAddress = async () => { - let ServerAddress = removeTrailingSlash(inputs.ServerAddress); - await updateOption('ServerAddress', ServerAddress); - }; + setEmailDomainWhitelist(newInputs.EmailDomainWhitelist.split(',').map((item) => { + return {key: item, text: item, value: item}; + })); + } else { + showError(message); + } + }; - const submitSMTP = async () => { - if (originInputs['SMTPServer'] !== inputs.SMTPServer) { - await updateOption('SMTPServer', inputs.SMTPServer); - } - if (originInputs['SMTPAccount'] !== inputs.SMTPAccount) { - await updateOption('SMTPAccount', inputs.SMTPAccount); - } - if (originInputs['SMTPFrom'] !== inputs.SMTPFrom) { - await updateOption('SMTPFrom', inputs.SMTPFrom); - } - if ( - originInputs['SMTPPort'] !== inputs.SMTPPort && - inputs.SMTPPort !== '' - ) { - await updateOption('SMTPPort', inputs.SMTPPort); - } - if ( - originInputs['SMTPToken'] !== inputs.SMTPToken && - inputs.SMTPToken !== '' - ) { - await updateOption('SMTPToken', inputs.SMTPToken); - } - }; + useEffect(() => { + getOptions().then(); + }, []); - - const submitEmailDomainWhitelist = async () => { - if ( - originInputs['EmailDomainWhitelist'] !== inputs.EmailDomainWhitelist.join(',') && - inputs.SMTPToken !== '' - ) { - await updateOption('EmailDomainWhitelist', inputs.EmailDomainWhitelist.join(',')); - } - }; - - const submitWeChat = async () => { - if (originInputs['WeChatServerAddress'] !== inputs.WeChatServerAddress) { - await updateOption( - 'WeChatServerAddress', - removeTrailingSlash(inputs.WeChatServerAddress) - ); - } - if ( - originInputs['WeChatAccountQRCodeImageURL'] !== - inputs.WeChatAccountQRCodeImageURL - ) { - await updateOption( - 'WeChatAccountQRCodeImageURL', - inputs.WeChatAccountQRCodeImageURL - ); - } - if ( - originInputs['WeChatServerToken'] !== inputs.WeChatServerToken && - inputs.WeChatServerToken !== '' - ) { - await updateOption('WeChatServerToken', inputs.WeChatServerToken); - } - }; - - const submitGitHubOAuth = async () => { - if (originInputs['GitHubClientId'] !== inputs.GitHubClientId) { - await updateOption('GitHubClientId', inputs.GitHubClientId); - } - if ( - originInputs['GitHubClientSecret'] !== inputs.GitHubClientSecret && - inputs.GitHubClientSecret !== '' - ) { - await updateOption('GitHubClientSecret', inputs.GitHubClientSecret); - } - }; - - const submitTurnstile = async () => { - if (originInputs['TurnstileSiteKey'] !== inputs.TurnstileSiteKey) { - await updateOption('TurnstileSiteKey', inputs.TurnstileSiteKey); - } - if ( - originInputs['TurnstileSecretKey'] !== inputs.TurnstileSecretKey && - inputs.TurnstileSecretKey !== '' - ) { - await updateOption('TurnstileSecretKey', inputs.TurnstileSecretKey); - } - }; - - const submitNewRestrictedDomain = () => { - const localDomainList = inputs.EmailDomainWhitelist; - if (restrictedDomainInput !== '' && !localDomainList.includes(restrictedDomainInput)) { - setRestrictedDomainInput(''); - setInputs({ - ...inputs, - EmailDomainWhitelist: [...localDomainList, restrictedDomainInput], - }); - setEmailDomainWhitelist([...EmailDomainWhitelist, { - key: restrictedDomainInput, - text: restrictedDomainInput, - value: restrictedDomainInput, - }]); - } - } - - return ( - - -
-
通用设置
- - - - - 更新服务器地址 - - -
配置登录注册
- - - { - showPasswordWarningModal && - setShowPasswordWarningModal(false)} - size={'tiny'} - style={{ maxWidth: '450px' }} - > - 警告 - -

取消密码登录将导致所有未绑定其他登录方式的用户(包括管理员)无法通过密码登录,确认取消?

-
- - - - -
+ const updateOption = async (key, value) => { + setLoading(true); + switch (key) { + case 'PasswordLoginEnabled': + case 'PasswordRegisterEnabled': + case 'EmailVerificationEnabled': + case 'GitHubOAuthEnabled': + case 'WeChatAuthEnabled': + case 'TurnstileCheckEnabled': + case 'EmailDomainRestrictionEnabled': + case 'RegisterEnabled': + value = inputs[key] === 'true' ? 'false' : 'true'; + break; + default: + break; + } + const res = await API.put('/api/option/', { + key, + value + }); + const {success, message} = res.data; + if (success) { + if (key === 'EmailDomainWhitelist') { + value = value.split(','); } - - - - -
- - - - - -
- 配置邮箱域名白名单 - 用以防止恶意用户利用临时邮箱批量注册 -
- - - - - - { - submitNewRestrictedDomain(); - }}>填入 - } - onKeyDown={(e) => { - if (e.key === 'Enter') { - submitNewRestrictedDomain(); - } - }} - autoComplete='new-password' - placeholder='输入新的允许的邮箱域名' - value={restrictedDomainInput} - onChange={(e, { value }) => { - setRestrictedDomainInput(value); - }} - /> - - 保存邮箱域名白名单设置 - -
- 配置 SMTP - 用以支持系统的邮件发送 -
- - - - - - - - - - 保存 SMTP 设置 - -
- 配置 GitHub OAuth App - - 用以支持通过 GitHub 进行登录注册, - - 点击此处 - - 管理你的 GitHub OAuth App - -
- - Homepage URL 填 {inputs.ServerAddress} - ,Authorization callback URL 填{' '} - {`${inputs.ServerAddress}/oauth/github`} - - - - - - - 保存 GitHub OAuth 设置 - - -
- 配置 WeChat Server - - 用以支持通过微信进行登录注册, - - 点击此处 - - 了解 WeChat Server - -
- - - - - - - 保存 WeChat Server 设置 - - -
- 配置 Turnstile - - 用以支持用户校验, - - 点击此处 - - 管理你的 Turnstile Sites,推荐选择 Invisible Widget Type - -
- - - - - - 保存 Turnstile 设置 - - -
-
- ); + setInputs((inputs) => ({ + ...inputs, [key]: value + })); + } else { + showError(message); + } + setLoading(false); + }; + + const handleInputChange = async (e, {name, value}) => { + if (name === 'PasswordLoginEnabled' && inputs[name] === 'true') { + // block disabling password login + setShowPasswordWarningModal(true); + return; + } + if ( + name === 'Notice' || + name.startsWith('SMTP') || + name === 'ServerAddress' || + name === 'EpayId' || + name === 'EpayKey' || + name === 'Price' || + name === 'PayAddress' || + name === 'GitHubClientId' || + name === 'GitHubClientSecret' || + name === 'WeChatServerAddress' || + name === 'WeChatServerToken' || + name === 'WeChatAccountQRCodeImageURL' || + name === 'TurnstileSiteKey' || + name === 'TurnstileSecretKey' || + name === 'EmailDomainWhitelist' + ) { + setInputs((inputs) => ({...inputs, [name]: value})); + } else { + await updateOption(name, value); + } + }; + + const submitServerAddress = async () => { + let ServerAddress = removeTrailingSlash(inputs.ServerAddress); + await updateOption('ServerAddress', ServerAddress); + }; + + const submitPayAddress = async () => { + if (inputs.ServerAddress === '') { + showError('请先填写服务器地址'); + return + } + let PayAddress = removeTrailingSlash(inputs.PayAddress); + await updateOption('PayAddress', PayAddress); + await updateOption('EpayId', inputs.EpayId); + await updateOption('EpayKey', inputs.EpayKey); + await updateOption('Price', inputs.Price); + }; + + const submitSMTP = async () => { + if (originInputs['SMTPServer'] !== inputs.SMTPServer) { + await updateOption('SMTPServer', inputs.SMTPServer); + } + if (originInputs['SMTPAccount'] !== inputs.SMTPAccount) { + await updateOption('SMTPAccount', inputs.SMTPAccount); + } + if (originInputs['SMTPFrom'] !== inputs.SMTPFrom) { + await updateOption('SMTPFrom', inputs.SMTPFrom); + } + if ( + originInputs['SMTPPort'] !== inputs.SMTPPort && + inputs.SMTPPort !== '' + ) { + await updateOption('SMTPPort', inputs.SMTPPort); + } + if ( + originInputs['SMTPToken'] !== inputs.SMTPToken && + inputs.SMTPToken !== '' + ) { + await updateOption('SMTPToken', inputs.SMTPToken); + } + }; + + + const submitEmailDomainWhitelist = async () => { + if ( + originInputs['EmailDomainWhitelist'] !== inputs.EmailDomainWhitelist.join(',') && + inputs.SMTPToken !== '' + ) { + await updateOption('EmailDomainWhitelist', inputs.EmailDomainWhitelist.join(',')); + } + }; + + const submitWeChat = async () => { + if (originInputs['WeChatServerAddress'] !== inputs.WeChatServerAddress) { + await updateOption( + 'WeChatServerAddress', + removeTrailingSlash(inputs.WeChatServerAddress) + ); + } + if ( + originInputs['WeChatAccountQRCodeImageURL'] !== + inputs.WeChatAccountQRCodeImageURL + ) { + await updateOption( + 'WeChatAccountQRCodeImageURL', + inputs.WeChatAccountQRCodeImageURL + ); + } + if ( + originInputs['WeChatServerToken'] !== inputs.WeChatServerToken && + inputs.WeChatServerToken !== '' + ) { + await updateOption('WeChatServerToken', inputs.WeChatServerToken); + } + }; + + const submitGitHubOAuth = async () => { + if (originInputs['GitHubClientId'] !== inputs.GitHubClientId) { + await updateOption('GitHubClientId', inputs.GitHubClientId); + } + if ( + originInputs['GitHubClientSecret'] !== inputs.GitHubClientSecret && + inputs.GitHubClientSecret !== '' + ) { + await updateOption('GitHubClientSecret', inputs.GitHubClientSecret); + } + }; + + const submitTurnstile = async () => { + if (originInputs['TurnstileSiteKey'] !== inputs.TurnstileSiteKey) { + await updateOption('TurnstileSiteKey', inputs.TurnstileSiteKey); + } + if ( + originInputs['TurnstileSecretKey'] !== inputs.TurnstileSecretKey && + inputs.TurnstileSecretKey !== '' + ) { + await updateOption('TurnstileSecretKey', inputs.TurnstileSecretKey); + } + }; + + const submitNewRestrictedDomain = () => { + const localDomainList = inputs.EmailDomainWhitelist; + if (restrictedDomainInput !== '' && !localDomainList.includes(restrictedDomainInput)) { + setRestrictedDomainInput(''); + setInputs({ + ...inputs, + EmailDomainWhitelist: [...localDomainList, restrictedDomainInput], + }); + setEmailDomainWhitelist([...EmailDomainWhitelist, { + key: restrictedDomainInput, + text: restrictedDomainInput, + value: restrictedDomainInput, + }]); + } + } + + return ( + + +
+
通用设置
+ + + + + 更新服务器地址 + + +
支付设置(当前仅支持易支付接口,使用上方服务器地址作为回调地址!)
+ + + + + + + + 更新支付地址 + + +
配置登录注册
+ + + { + showPasswordWarningModal && + setShowPasswordWarningModal(false)} + size={'tiny'} + style={{maxWidth: '450px'}} + > + 警告 + +

取消密码登录将导致所有未绑定其他登录方式的用户(包括管理员)无法通过密码登录,确认取消?

+
+ + + + +
+ } + + + + +
+ + + + + +
+ 配置邮箱域名白名单 + 用以防止恶意用户利用临时邮箱批量注册 +
+ + + + + + { + submitNewRestrictedDomain(); + }}>填入 + } + onKeyDown={(e) => { + if (e.key === 'Enter') { + submitNewRestrictedDomain(); + } + }} + autoComplete='new-password' + placeholder='输入新的允许的邮箱域名' + value={restrictedDomainInput} + onChange={(e, {value}) => { + setRestrictedDomainInput(value); + }} + /> + + 保存邮箱域名白名单设置 + +
+ 配置 SMTP + 用以支持系统的邮件发送 +
+ + + + + + + + + + 保存 SMTP 设置 + +
+ 配置 GitHub OAuth App + + 用以支持通过 GitHub 进行登录注册, + + 点击此处 + + 管理你的 GitHub OAuth App + +
+ + Homepage URL 填 {inputs.ServerAddress} + ,Authorization callback URL 填{' '} + {`${inputs.ServerAddress}/oauth/github`} + + + + + + + 保存 GitHub OAuth 设置 + + +
+ 配置 WeChat Server + + 用以支持通过微信进行登录注册, + + 点击此处 + + 了解 WeChat Server + +
+ + + + + + + 保存 WeChat Server 设置 + + +
+ 配置 Turnstile + + 用以支持用户校验, + + 点击此处 + + 管理你的 Turnstile Sites,推荐选择 Invisible Widget Type + +
+ + + + + + 保存 Turnstile 设置 + + +
+
+ ); }; export default SystemSetting; diff --git a/web/src/pages/Home/index.js b/web/src/pages/Home/index.js index 67944f91..6009486a 100644 --- a/web/src/pages/Home/index.js +++ b/web/src/pages/Home/index.js @@ -1,8 +1,8 @@ -import React, {useContext, useEffect, useState} from 'react'; -import {Card, Grid, Header, Segment} from 'semantic-ui-react'; -import {API, showError, showNotice, timestamp2string} from '../../helpers'; -import {StatusContext} from '../../context/Status'; -import {marked} from 'marked'; +import React, { useContext, useEffect, useState } from 'react'; +import { Card, Grid, Header, Segment } from 'semantic-ui-react'; +import { API, showError, showNotice, timestamp2string } from '../../helpers'; +import { StatusContext } from '../../context/Status'; +import { marked } from 'marked'; const Home = () => { const [statusState, statusDispatch] = useContext(StatusContext); @@ -11,11 +11,12 @@ const Home = () => { const displayNotice = async () => { const res = await API.get('/api/notice'); - const {success, message, data} = res.data; + const { success, message, data } = res.data; if (success) { let oldNotice = localStorage.getItem('notice'); if (data !== oldNotice && data !== '') { - showNotice(data); + const htmlNotice = marked(data); + showNotice(htmlNotice, true); localStorage.setItem('notice', data); } } else { @@ -26,7 +27,7 @@ const Home = () => { const displayHomePageContent = async () => { setHomePageContent(localStorage.getItem('home_page_content') || ''); const res = await API.get('/api/home_page_content'); - const {success, message, data} = res.data; + const { success, message, data } = res.data; if (success) { let content = data; if (!data.startsWith('https://')) { @@ -53,20 +54,28 @@ const Home = () => { return ( <> { - // homePageContentLoaded && homePageContent === '' ? - <> + homePageContentLoaded && homePageContent === '' ? <> -
当前状态
+
系统状况
- GPT-3.5 - 信息总览 + 系统信息 + 系统信息总览 -

通道:官方通道

-

状态:存活

-

价格:{statusState?.status?.base_price}R / 刀

+

名称:{statusState?.status?.system_name}

+

版本:{statusState?.status?.version ? statusState?.status?.version : "unknown"}

+

+ 源码: + + https://github.com/songquanpeng/one-api + +

+

启动时间:{getStartTimeString()}

@@ -74,26 +83,32 @@ const Home = () => { - GPT-4 - 信息总览 + 系统配置 + 系统配置总览 -

通道:官方通道|低价通道

- 状态: - {statusState?.status?.stable_price===-1? - 不   可   用 - : - 可  用 - } - | - {statusState?.status?.normal_price===-1? - 不   可   用 - : - 可  用 - } + 邮箱验证: + {statusState?.status?.email_verification === true + ? '已启用' + : '未启用'}

- 价格:{statusState?.status?.stable_price}R / 刀|{statusState?.status?.normal_price}R / 刀 + GitHub 身份验证: + {statusState?.status?.github_oauth === true + ? '已启用' + : '未启用'} +

+

+ 微信身份验证: + {statusState?.status?.wechat_login === true + ? '已启用' + : '未启用'} +

+

+ Turnstile 用户校验: + {statusState?.status?.turnstile_check === true + ? '已启用' + : '未启用'}

@@ -101,21 +116,18 @@ const Home = () => {
- { - homePageContent.startsWith('https://') ?