Kaynağa Gözat

移除不必要的功能

CaIon 2 yıl önce
ebeveyn
işleme
9ab4f7a271

+ 4 - 4
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

+ 66 - 72
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)
 			}
 		}
 	}

+ 4 - 3
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

+ 1 - 0
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

+ 0 - 6
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)
 	}

+ 35 - 60
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))})
 }

+ 11 - 41
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)

+ 1 - 0
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 {

+ 12 - 11
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":

+ 154 - 233
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 handleSystemTokenClick = async (e) => {
-    e.target.select();
-    await copy(e.target.value);
-    showSuccess(`系统令牌已复制到剪切板`);
-  };const deleteAccount = async () => {
+    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);
-    }
-  };
-
-  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);
-    }
-  };
+        if (success) {
+            showSuccess('账户已删除!');
+            await API.get('/api/user/logout');
+            userDispatch({ type: 'logout' });
+            localStorage.removeItem('user');
+            navigate('/login');
+        } else {
+            showError(message);
+        }
+    };
 
-  const openGitHubOAuth = () => {
-    window.open(
-      `https://github.com/login/oauth/authorize?client_id=${status.github_client_id}&scope=user:email`
-    );
-  };
+    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 openGitHubOAuth = () => {
+        window.open(
+            `https://github.com/login/oauth/authorize?client_id=${status.github_client_id}&scope=user:email`
+        );
+    };
 
-  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 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 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 (
-        <div style={{lineHeight: '40px'}}>
+        <div style={{ lineHeight: '40px' }}>
             <Header as='h3'>通用设置</Header>
             <Message>
                 注意,此处生成的令牌用于系统管理,而非用于请求 OpenAI 相关的服务,请知悉。
@@ -209,84 +186,26 @@ const PersonalSetting = () => {
             <Button onClick={() => {
                 setShowAccountDeleteModal(true);
             }}>删除个人账户</Button>
-            <Divider/>
-            <Header as='h3'>GPT-4消费设置</Header>
-            <Form>
-                <Form.Field>
-                    <Checkbox label="启用稳定模式(当低价渠道宕机时,自动选择已开启的渠道,以保证稳定性,仅影响GPT-4)"
-                              checked={stableMode.stableMode}
-                              onChange={
-                                (e, data) => {
-                                    setStableMode({
-                                        ...stableMode,
-                                        stableMode: data.checked
-                                    })
-                                }
-                              }
-                    ></Checkbox>
-                </Form.Field>
-                <Form.Field
-                    control={Input}
-                    label='最高接受价格(n元/刀)'
-                    placeholder='7'
-                    type={'number'}
-                    value={stableMode.maxPrice}
-                    onChange={
-                        (e, data) => {
-                            setStableMode({
-                                ...stableMode,
-                                maxPrice: data.value
-                            })
-                        }
-                    }
-                >
-                    {/*<label></label>*/}
-                    {/*<input placeholder='7' value= {stableMode.maxPrice}/>*/}
-                </Form.Field>
-                <Button type='submit' onClick={
-                    async (e, data) => {
-                        if (stableMode.localMaxPrice === '') return;
-                        // console.log(data)
-                        // post to /api/user/set_stable_mode
-                        const res = await API.post(`/api/user/set_stable_mode`, stableMode)
-                        const {success, message} = res.data;
-                        if (success) {
-                            // userDispatch({type: 'stable_mode', payload: stableMode})
-                            userState.user.stable_mode = stableMode.stableMode
-                            userState.user.max_price = stableMode.maxPrice
-                            localStorage.setItem('user', JSON.stringify(userState.user));
-                            showSuccess('设置成功!');
-                        } else {
-                            showError(message);
-                        }
-                    }
-                }>保存消费设置</Button>
-            </Form>
-            {/*<Checkbox label="启用稳定模式(当低价渠道宕机时,自动选择已开启的渠道,以保证稳定性)" onChange={*/}
-            {/*    (e, data) => {*/}
-            {/*        // if (inputs.email_verification_code === '') return;*/}
-            {/*        console.log(data)*/}
-            {/*    }*/}
-            {/*}></Checkbox>*/}
-            {/*<Input label="最高接受价格(n元/刀)" type="integer"></Input>*/}
+
             {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/>
+                <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 && (
@@ -307,8 +226,8 @@ const PersonalSetting = () => {
             >
                 <Modal.Content>
                     <Modal.Description>
-                        <Image src={status.wechat_qrcode} fluid/>
-                        <div style={{textAlign: 'center'}}>
+                        <Image src={status.wechat_qrcode} fluid />
+                        <div style={{ textAlign: 'center' }}>
                             <p>
                                 微信扫码关注公众号,输入「验证码」获取验证码(三分钟内有效)
                             </p>
@@ -345,7 +264,7 @@ const PersonalSetting = () => {
                 onOpen={() => setShowEmailBindModal(true)}
                 open={showEmailBindModal}
                 size={'tiny'}
-                style={{maxWidth: '450px'}}
+                style={{ maxWidth: '450px' }}
             >
                 <Modal.Header>绑定邮箱地址</Modal.Header>
                 <Modal.Content>
@@ -381,25 +300,25 @@ const PersonalSetting = () => {
                                 <></>
                             )}
                             <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>
+                                <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>
@@ -408,11 +327,11 @@ const PersonalSetting = () => {
                 onOpen={() => setShowAccountDeleteModal(true)}
                 open={showAccountDeleteModal}
                 size={'tiny'}
-                style={{maxWidth: '450px'}}
+                style={{ maxWidth: '450px' }}
             >
                 <Modal.Header>危险操作</Modal.Header>
-        <Modal.Content>
-        <Message>您正在删除自己的帐户,将清空所有数据且不可恢复</Message>
+                <Modal.Content>
+                    <Message>您正在删除自己的帐户,将清空所有数据且不可恢复</Message>
                     <Modal.Description>
                         <Form size='large'>
                             <Form.Input
@@ -431,24 +350,26 @@ const PersonalSetting = () => {
                                 />
                             ) : (
                                 <></>
-                            )}<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>
+                            )}
+                            <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>
@@ -457,4 +378,4 @@ const PersonalSetting = () => {
     );
 };
 
-export default PersonalSetting;
+export default PersonalSetting;

+ 574 - 517
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);
+    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 [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);
+    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);
-    }
-  };
+            setEmailDomainWhitelist(newInputs.EmailDomainWhitelist.split(',').map((item) => {
+                return {key: item, text: item, value: item};
+            }));
+        } else {
+            showError(message);
+        }
+    };
 
-  useEffect(() => {
-    getOptions().then();
-  }, []);
+    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
-    });
-    const { success, message } = res.data;
-    if (success) {
-      if (key === 'EmailDomainWhitelist') {
-        value = value.split(',');
-      }
-      setInputs((inputs) => ({
-        ...inputs, [key]: value
-      }));
-    } else {
-      showError(message);
-    }
-    setLoading(false);
-  };
+    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(',');
+            }
+            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 === '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 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 submitServerAddress = async () => {
+        let ServerAddress = removeTrailingSlash(inputs.ServerAddress);
+        await updateOption('ServerAddress', ServerAddress);
+    };
 
-  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 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 submitEmailDomainWhitelist = async () => {
+        if (
+            originInputs['EmailDomainWhitelist'] !== inputs.EmailDomainWhitelist.join(',') &&
+            inputs.SMTPToken !== ''
+        ) {
+            await updateOption('EmailDomainWhitelist', inputs.EmailDomainWhitelist.join(','));
+        }
+    };
 
-  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 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 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 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 submitNewRestrictedDomain = () => {
-    const localDomainList = inputs.EmailDomainWhitelist;
-    if (restrictedDomainInput !== '' && !localDomainList.includes(restrictedDomainInput)) {
-      setRestrictedDomainInput('');
-      setInputs({
-        ...inputs,
-        EmailDomainWhitelist: [...localDomainList, restrictedDomainInput],
-      });
-      setEmailDomainWhitelist([...EmailDomainWhitelist, {
-        key: restrictedDomainInput,
-        text: restrictedDomainInput,
-        value: restrictedDomainInput,
-      }]);
+    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 (
-    <Grid columns={1}>
-      <Grid.Column>
-        <Form loading={loading}>
-          <Header as='h3'>通用设置</Header>
-          <Form.Group widths='equal'>
-            <Form.Input
-              label='服务器地址'
-              placeholder='例如:https://yourdomain.com'
-              value={inputs.ServerAddress}
-              name='ServerAddress'
-              onChange={handleInputChange}
-            />
-          </Form.Group>
-          <Form.Button onClick={submitServerAddress}>
-            更新服务器地址
-          </Form.Button>
-          <Divider />
-          <Header as='h3'>配置登录注册</Header>
-          <Form.Group inline>
-            <Form.Checkbox
-              checked={inputs.PasswordLoginEnabled === 'true'}
-              label='允许通过密码进行登录'
-              name='PasswordLoginEnabled'
-              onChange={handleInputChange}
-            />
-            {
-              showPasswordWarningModal &&
-              <Modal
-                open={showPasswordWarningModal}
-                onClose={() => setShowPasswordWarningModal(false)}
-                size={'tiny'}
-                style={{ maxWidth: '450px' }}
-              >
-                <Modal.Header>警告</Modal.Header>
-                <Modal.Content>
-                  <p>取消密码登录将导致所有未绑定其他登录方式的用户(包括管理员)无法通过密码登录,确认取消?</p>
-                </Modal.Content>
-                <Modal.Actions>
-                  <Button onClick={() => setShowPasswordWarningModal(false)}>取消</Button>
-                  <Button
-                    color='yellow'
-                    onClick={async () => {
-                      setShowPasswordWarningModal(false);
-                      await updateOption('PasswordLoginEnabled', 'false');
-                    }}
-                  >
-                    确定
-                  </Button>
-                </Modal.Actions>
-              </Modal>
-            }
-            <Form.Checkbox
-              checked={inputs.PasswordRegisterEnabled === 'true'}
-              label='允许通过密码进行注册'
-              name='PasswordRegisterEnabled'
-              onChange={handleInputChange}
-            />
-            <Form.Checkbox
-              checked={inputs.EmailVerificationEnabled === 'true'}
-              label='通过密码注册时需要进行邮箱验证'
-              name='EmailVerificationEnabled'
-              onChange={handleInputChange}
-            />
-            <Form.Checkbox
-              checked={inputs.GitHubOAuthEnabled === 'true'}
-              label='允许通过 GitHub 账户登录 & 注册'
-              name='GitHubOAuthEnabled'
-              onChange={handleInputChange}
-            />
-            <Form.Checkbox
-              checked={inputs.WeChatAuthEnabled === 'true'}
-              label='允许通过微信登录 & 注册'
-              name='WeChatAuthEnabled'
-              onChange={handleInputChange}
-            />
-          </Form.Group>
-          <Form.Group inline>
-            <Form.Checkbox
-              checked={inputs.RegisterEnabled === 'true'}
-              label='允许新用户注册(此项为否时,新用户将无法以任何方式进行注册)'
-              name='RegisterEnabled'
-              onChange={handleInputChange}
-            />
-            <Form.Checkbox
-              checked={inputs.TurnstileCheckEnabled === 'true'}
-              label='启用 Turnstile 用户校验'
-              name='TurnstileCheckEnabled'
-              onChange={handleInputChange}
-            />
-          </Form.Group>
-          <Divider />
-          <Header as='h3'>
-            配置邮箱域名白名单
-            <Header.Subheader>用以防止恶意用户利用临时邮箱批量注册</Header.Subheader>
-          </Header>
-          <Form.Group widths={3}>
-            <Form.Checkbox
-              label='启用邮箱域名白名单'
-              name='EmailDomainRestrictionEnabled'
-              onChange={handleInputChange}
-              checked={inputs.EmailDomainRestrictionEnabled === 'true'}
-            />
-          </Form.Group>
-          <Form.Group widths={2}>
-            <Form.Dropdown
-              label='允许的邮箱域名'
-              placeholder='允许的邮箱域名'
-              name='EmailDomainWhitelist'
-              required
-              fluid
-              multiple
-              selection
-              onChange={handleInputChange}
-              value={inputs.EmailDomainWhitelist}
-              autoComplete='new-password'
-              options={EmailDomainWhitelist}
-            />
-            <Form.Input
-              label='添加新的允许的邮箱域名'
-              action={
-                <Button type='button' onClick={() => {
-                  submitNewRestrictedDomain();
-                }}>填入</Button>
-              }
-              onKeyDown={(e) => {
-                if (e.key === 'Enter') {
-                  submitNewRestrictedDomain();
-                }
-              }}
-              autoComplete='new-password'
-              placeholder='输入新的允许的邮箱域名'
-              value={restrictedDomainInput}
-              onChange={(e, { value }) => {
-                setRestrictedDomainInput(value);
-              }}
-            />
-          </Form.Group>
-          <Form.Button onClick={submitEmailDomainWhitelist}>保存邮箱域名白名单设置</Form.Button>
-          <Divider />
-          <Header as='h3'>
-            配置 SMTP
-            <Header.Subheader>用以支持系统的邮件发送</Header.Subheader>
-          </Header>
-          <Form.Group widths={3}>
-            <Form.Input
-              label='SMTP 服务器地址'
-              name='SMTPServer'
-              onChange={handleInputChange}
-              autoComplete='new-password'
-              value={inputs.SMTPServer}
-              placeholder='例如:smtp.qq.com'
-            />
-            <Form.Input
-              label='SMTP 端口'
-              name='SMTPPort'
-              onChange={handleInputChange}
-              autoComplete='new-password'
-              value={inputs.SMTPPort}
-              placeholder='默认: 587'
-            />
-            <Form.Input
-              label='SMTP 账户'
-              name='SMTPAccount'
-              onChange={handleInputChange}
-              autoComplete='new-password'
-              value={inputs.SMTPAccount}
-              placeholder='通常是邮箱地址'
-            />
-          </Form.Group>
-          <Form.Group widths={3}>
-            <Form.Input
-              label='SMTP 发送者邮箱'
-              name='SMTPFrom'
-              onChange={handleInputChange}
-              autoComplete='new-password'
-              value={inputs.SMTPFrom}
-              placeholder='通常和邮箱地址保持一致'
-            />
-            <Form.Input
-              label='SMTP 访问凭证'
-              name='SMTPToken'
-              onChange={handleInputChange}
-              type='password'
-              autoComplete='new-password'
-              checked={inputs.RegisterEnabled === 'true'}
-              placeholder='敏感信息不会发送到前端显示'
-            />
-          </Form.Group>
-          <Form.Button onClick={submitSMTP}>保存 SMTP 设置</Form.Button>
-          <Divider />
-          <Header as='h3'>
-            配置 GitHub OAuth App
-            <Header.Subheader>
-              用以支持通过 GitHub 进行登录注册,
-              <a href='https://github.com/settings/developers' target='_blank'>
-                点击此处
-              </a>
-              管理你的 GitHub OAuth App
-            </Header.Subheader>
-          </Header>
-          <Message>
-            Homepage URL 填 <code>{inputs.ServerAddress}</code>
-            ,Authorization callback URL 填{' '}
-            <code>{`${inputs.ServerAddress}/oauth/github`}</code>
-          </Message>
-          <Form.Group widths={3}>
-            <Form.Input
-              label='GitHub Client ID'
-              name='GitHubClientId'
-              onChange={handleInputChange}
-              autoComplete='new-password'
-              value={inputs.GitHubClientId}
-              placeholder='输入你注册的 GitHub OAuth APP 的 ID'
-            />
-            <Form.Input
-              label='GitHub Client Secret'
-              name='GitHubClientSecret'
-              onChange={handleInputChange}
-              type='password'
-              autoComplete='new-password'
-              value={inputs.GitHubClientSecret}
-              placeholder='敏感信息不会发送到前端显示'
-            />
-          </Form.Group>
-          <Form.Button onClick={submitGitHubOAuth}>
-            保存 GitHub OAuth 设置
-          </Form.Button>
-          <Divider />
-          <Header as='h3'>
-            配置 WeChat Server
-            <Header.Subheader>
-              用以支持通过微信进行登录注册,
-              <a
-                href='https://github.com/songquanpeng/wechat-server'
-                target='_blank'
-              >
-                点击此处
-              </a>
-              了解 WeChat Server
-            </Header.Subheader>
-          </Header>
-          <Form.Group widths={3}>
-            <Form.Input
-              label='WeChat Server 服务器地址'
-              name='WeChatServerAddress'
-              placeholder='例如:https://yourdomain.com'
-              onChange={handleInputChange}
-              autoComplete='new-password'
-              value={inputs.WeChatServerAddress}
-            />
-            <Form.Input
-              label='WeChat Server 访问凭证'
-              name='WeChatServerToken'
-              type='password'
-              onChange={handleInputChange}
-              autoComplete='new-password'
-              value={inputs.WeChatServerToken}
-              placeholder='敏感信息不会发送到前端显示'
-            />
-            <Form.Input
-              label='微信公众号二维码图片链接'
-              name='WeChatAccountQRCodeImageURL'
-              onChange={handleInputChange}
-              autoComplete='new-password'
-              value={inputs.WeChatAccountQRCodeImageURL}
-              placeholder='输入一个图片链接'
-            />
-          </Form.Group>
-          <Form.Button onClick={submitWeChat}>
-            保存 WeChat Server 设置
-          </Form.Button>
-          <Divider />
-          <Header as='h3'>
-            配置 Turnstile
-            <Header.Subheader>
-              用以支持用户校验,
-              <a href='https://dash.cloudflare.com/' target='_blank'>
-                点击此处
-              </a>
-              管理你的 Turnstile Sites,推荐选择 Invisible Widget Type
-            </Header.Subheader>
-          </Header>
-          <Form.Group widths={3}>
-            <Form.Input
-              label='Turnstile Site Key'
-              name='TurnstileSiteKey'
-              onChange={handleInputChange}
-              autoComplete='new-password'
-              value={inputs.TurnstileSiteKey}
-              placeholder='输入你注册的 Turnstile Site Key'
-            />
-            <Form.Input
-              label='Turnstile Secret Key'
-              name='TurnstileSecretKey'
-              onChange={handleInputChange}
-              type='password'
-              autoComplete='new-password'
-              value={inputs.TurnstileSecretKey}
-              placeholder='敏感信息不会发送到前端显示'
-            />
-          </Form.Group>
-          <Form.Button onClick={submitTurnstile}>
-            保存 Turnstile 设置
-          </Form.Button>
-        </Form>
-      </Grid.Column>
-    </Grid>
-  );
+    return (
+        <Grid columns={1}>
+            <Grid.Column>
+                <Form loading={loading}>
+                    <Header as='h3'>通用设置</Header>
+                    <Form.Group widths='equal'>
+                        <Form.Input
+                            label='服务器地址'
+                            placeholder='例如:https://yourdomain.com'
+                            value={inputs.ServerAddress}
+                            name='ServerAddress'
+                            onChange={handleInputChange}
+                        />
+                    </Form.Group>
+                    <Form.Button onClick={submitServerAddress}>
+                        更新服务器地址
+                    </Form.Button>
+                    <Divider/>
+                    <Header as='h3'>支付设置(当前仅支持易支付接口,使用上方服务器地址作为回调地址!)</Header>
+                    <Form.Group widths='equal'>
+                        <Form.Input
+                            label='支付地址,不填写则不启用在线支付'
+                            placeholder='例如:https://yourdomain.com'
+                            value={inputs.PayAddress}
+                            name='PayAddress'
+                            onChange={handleInputChange}
+                        />
+                        <Form.Input
+                            label='易支付商户ID'
+                            placeholder='例如:0001'
+                            value={inputs.EpayId}
+                            name='EpayId'
+                            onChange={handleInputChange}
+                        />
+                        <Form.Input
+                            label='易支付商户密钥'
+                            placeholder='例如:dejhfueqhujasjmndbjkqaw'
+                            value={inputs.EpayKey}
+                            name='EpayKey'
+                            onChange={handleInputChange}
+                        />
+                        <Form.Input
+                            label='充值价格(x元/美金)'
+                            placeholder='例如:7,就是7元/美金'
+                            value={inputs.Price}
+                            name='Price'
+                            type='number'
+                            min={0}
+                            onChange={handleInputChange}
+                        />
+                    </Form.Group>
+                    <Form.Button onClick={submitPayAddress}>
+                        更新支付地址
+                    </Form.Button>
+                    <Divider/>
+                    <Header as='h3'>配置登录注册</Header>
+                    <Form.Group inline>
+                        <Form.Checkbox
+                            checked={inputs.PasswordLoginEnabled === 'true'}
+                            label='允许通过密码进行登录'
+                            name='PasswordLoginEnabled'
+                            onChange={handleInputChange}
+                        />
+                        {
+                            showPasswordWarningModal &&
+                            <Modal
+                                open={showPasswordWarningModal}
+                                onClose={() => setShowPasswordWarningModal(false)}
+                                size={'tiny'}
+                                style={{maxWidth: '450px'}}
+                            >
+                                <Modal.Header>警告</Modal.Header>
+                                <Modal.Content>
+                                    <p>取消密码登录将导致所有未绑定其他登录方式的用户(包括管理员)无法通过密码登录,确认取消?</p>
+                                </Modal.Content>
+                                <Modal.Actions>
+                                    <Button onClick={() => setShowPasswordWarningModal(false)}>取消</Button>
+                                    <Button
+                                        color='yellow'
+                                        onClick={async () => {
+                                            setShowPasswordWarningModal(false);
+                                            await updateOption('PasswordLoginEnabled', 'false');
+                                        }}
+                                    >
+                                        确定
+                                    </Button>
+                                </Modal.Actions>
+                            </Modal>
+                        }
+                        <Form.Checkbox
+                            checked={inputs.PasswordRegisterEnabled === 'true'}
+                            label='允许通过密码进行注册'
+                            name='PasswordRegisterEnabled'
+                            onChange={handleInputChange}
+                        />
+                        <Form.Checkbox
+                            checked={inputs.EmailVerificationEnabled === 'true'}
+                            label='通过密码注册时需要进行邮箱验证'
+                            name='EmailVerificationEnabled'
+                            onChange={handleInputChange}
+                        />
+                        <Form.Checkbox
+                            checked={inputs.GitHubOAuthEnabled === 'true'}
+                            label='允许通过 GitHub 账户登录 & 注册'
+                            name='GitHubOAuthEnabled'
+                            onChange={handleInputChange}
+                        />
+                        <Form.Checkbox
+                            checked={inputs.WeChatAuthEnabled === 'true'}
+                            label='允许通过微信登录 & 注册'
+                            name='WeChatAuthEnabled'
+                            onChange={handleInputChange}
+                        />
+                    </Form.Group>
+                    <Form.Group inline>
+                        <Form.Checkbox
+                            checked={inputs.RegisterEnabled === 'true'}
+                            label='允许新用户注册(此项为否时,新用户将无法以任何方式进行注册)'
+                            name='RegisterEnabled'
+                            onChange={handleInputChange}
+                        />
+                        <Form.Checkbox
+                            checked={inputs.TurnstileCheckEnabled === 'true'}
+                            label='启用 Turnstile 用户校验'
+                            name='TurnstileCheckEnabled'
+                            onChange={handleInputChange}
+                        />
+                    </Form.Group>
+                    <Divider/>
+                    <Header as='h3'>
+                        配置邮箱域名白名单
+                        <Header.Subheader>用以防止恶意用户利用临时邮箱批量注册</Header.Subheader>
+                    </Header>
+                    <Form.Group widths={3}>
+                        <Form.Checkbox
+                            label='启用邮箱域名白名单'
+                            name='EmailDomainRestrictionEnabled'
+                            onChange={handleInputChange}
+                            checked={inputs.EmailDomainRestrictionEnabled === 'true'}
+                        />
+                    </Form.Group>
+                    <Form.Group widths={2}>
+                        <Form.Dropdown
+                            label='允许的邮箱域名'
+                            placeholder='允许的邮箱域名'
+                            name='EmailDomainWhitelist'
+                            required
+                            fluid
+                            multiple
+                            selection
+                            onChange={handleInputChange}
+                            value={inputs.EmailDomainWhitelist}
+                            autoComplete='new-password'
+                            options={EmailDomainWhitelist}
+                        />
+                        <Form.Input
+                            label='添加新的允许的邮箱域名'
+                            action={
+                                <Button type='button' onClick={() => {
+                                    submitNewRestrictedDomain();
+                                }}>填入</Button>
+                            }
+                            onKeyDown={(e) => {
+                                if (e.key === 'Enter') {
+                                    submitNewRestrictedDomain();
+                                }
+                            }}
+                            autoComplete='new-password'
+                            placeholder='输入新的允许的邮箱域名'
+                            value={restrictedDomainInput}
+                            onChange={(e, {value}) => {
+                                setRestrictedDomainInput(value);
+                            }}
+                        />
+                    </Form.Group>
+                    <Form.Button onClick={submitEmailDomainWhitelist}>保存邮箱域名白名单设置</Form.Button>
+                    <Divider/>
+                    <Header as='h3'>
+                        配置 SMTP
+                        <Header.Subheader>用以支持系统的邮件发送</Header.Subheader>
+                    </Header>
+                    <Form.Group widths={3}>
+                        <Form.Input
+                            label='SMTP 服务器地址'
+                            name='SMTPServer'
+                            onChange={handleInputChange}
+                            autoComplete='new-password'
+                            value={inputs.SMTPServer}
+                            placeholder='例如:smtp.qq.com'
+                        />
+                        <Form.Input
+                            label='SMTP 端口'
+                            name='SMTPPort'
+                            onChange={handleInputChange}
+                            autoComplete='new-password'
+                            value={inputs.SMTPPort}
+                            placeholder='默认: 587'
+                        />
+                        <Form.Input
+                            label='SMTP 账户'
+                            name='SMTPAccount'
+                            onChange={handleInputChange}
+                            autoComplete='new-password'
+                            value={inputs.SMTPAccount}
+                            placeholder='通常是邮箱地址'
+                        />
+                    </Form.Group>
+                    <Form.Group widths={3}>
+                        <Form.Input
+                            label='SMTP 发送者邮箱'
+                            name='SMTPFrom'
+                            onChange={handleInputChange}
+                            autoComplete='new-password'
+                            value={inputs.SMTPFrom}
+                            placeholder='通常和邮箱地址保持一致'
+                        />
+                        <Form.Input
+                            label='SMTP 访问凭证'
+                            name='SMTPToken'
+                            onChange={handleInputChange}
+                            type='password'
+                            autoComplete='new-password'
+                            checked={inputs.RegisterEnabled === 'true'}
+                            placeholder='敏感信息不会发送到前端显示'
+                        />
+                    </Form.Group>
+                    <Form.Button onClick={submitSMTP}>保存 SMTP 设置</Form.Button>
+                    <Divider/>
+                    <Header as='h3'>
+                        配置 GitHub OAuth App
+                        <Header.Subheader>
+                            用以支持通过 GitHub 进行登录注册,
+                            <a href='https://github.com/settings/developers' target='_blank'>
+                                点击此处
+                            </a>
+                            管理你的 GitHub OAuth App
+                        </Header.Subheader>
+                    </Header>
+                    <Message>
+                        Homepage URL 填 <code>{inputs.ServerAddress}</code>
+                        ,Authorization callback URL 填{' '}
+                        <code>{`${inputs.ServerAddress}/oauth/github`}</code>
+                    </Message>
+                    <Form.Group widths={3}>
+                        <Form.Input
+                            label='GitHub Client ID'
+                            name='GitHubClientId'
+                            onChange={handleInputChange}
+                            autoComplete='new-password'
+                            value={inputs.GitHubClientId}
+                            placeholder='输入你注册的 GitHub OAuth APP 的 ID'
+                        />
+                        <Form.Input
+                            label='GitHub Client Secret'
+                            name='GitHubClientSecret'
+                            onChange={handleInputChange}
+                            type='password'
+                            autoComplete='new-password'
+                            value={inputs.GitHubClientSecret}
+                            placeholder='敏感信息不会发送到前端显示'
+                        />
+                    </Form.Group>
+                    <Form.Button onClick={submitGitHubOAuth}>
+                        保存 GitHub OAuth 设置
+                    </Form.Button>
+                    <Divider/>
+                    <Header as='h3'>
+                        配置 WeChat Server
+                        <Header.Subheader>
+                            用以支持通过微信进行登录注册,
+                            <a
+                                href='https://github.com/songquanpeng/wechat-server'
+                                target='_blank'
+                            >
+                                点击此处
+                            </a>
+                            了解 WeChat Server
+                        </Header.Subheader>
+                    </Header>
+                    <Form.Group widths={3}>
+                        <Form.Input
+                            label='WeChat Server 服务器地址'
+                            name='WeChatServerAddress'
+                            placeholder='例如:https://yourdomain.com'
+                            onChange={handleInputChange}
+                            autoComplete='new-password'
+                            value={inputs.WeChatServerAddress}
+                        />
+                        <Form.Input
+                            label='WeChat Server 访问凭证'
+                            name='WeChatServerToken'
+                            type='password'
+                            onChange={handleInputChange}
+                            autoComplete='new-password'
+                            value={inputs.WeChatServerToken}
+                            placeholder='敏感信息不会发送到前端显示'
+                        />
+                        <Form.Input
+                            label='微信公众号二维码图片链接'
+                            name='WeChatAccountQRCodeImageURL'
+                            onChange={handleInputChange}
+                            autoComplete='new-password'
+                            value={inputs.WeChatAccountQRCodeImageURL}
+                            placeholder='输入一个图片链接'
+                        />
+                    </Form.Group>
+                    <Form.Button onClick={submitWeChat}>
+                        保存 WeChat Server 设置
+                    </Form.Button>
+                    <Divider/>
+                    <Header as='h3'>
+                        配置 Turnstile
+                        <Header.Subheader>
+                            用以支持用户校验,
+                            <a href='https://dash.cloudflare.com/' target='_blank'>
+                                点击此处
+                            </a>
+                            管理你的 Turnstile Sites,推荐选择 Invisible Widget Type
+                        </Header.Subheader>
+                    </Header>
+                    <Form.Group widths={3}>
+                        <Form.Input
+                            label='Turnstile Site Key'
+                            name='TurnstileSiteKey'
+                            onChange={handleInputChange}
+                            autoComplete='new-password'
+                            value={inputs.TurnstileSiteKey}
+                            placeholder='输入你注册的 Turnstile Site Key'
+                        />
+                        <Form.Input
+                            label='Turnstile Secret Key'
+                            name='TurnstileSecretKey'
+                            onChange={handleInputChange}
+                            type='password'
+                            autoComplete='new-password'
+                            value={inputs.TurnstileSecretKey}
+                            placeholder='敏感信息不会发送到前端显示'
+                        />
+                    </Form.Group>
+                    <Form.Button onClick={submitTurnstile}>
+                        保存 Turnstile 设置
+                    </Form.Button>
+                </Form>
+            </Grid.Column>
+        </Grid>
+    );
 };
 
 export default SystemSetting;

+ 55 - 43
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 === '' ? <>
                     <Segment>
-                        <Header as='h3'>当前状态</Header>
+                        <Header as='h3'>系统状况</Header>
                         <Grid columns={2} stackable>
                             <Grid.Column>
                                 <Card fluid>
                                     <Card.Content>
-                                        <Card.Header>GPT-3.5</Card.Header>
-                                        <Card.Meta>信息总览</Card.Meta>
+                                        <Card.Header>系统信息</Card.Header>
+                                        <Card.Meta>系统信息总览</Card.Meta>
                                         <Card.Description>
-                                            <p>通道:官方通道</p>
-                                            <p>状态:存活</p>
-                                            <p>价格:{statusState?.status?.base_price}R&nbsp;/&nbsp;刀</p>
+                                            <p>名称:{statusState?.status?.system_name}</p>
+                                            <p>版本:{statusState?.status?.version ? statusState?.status?.version : "unknown"}</p>
+                                            <p>
+                                                源码:
+                                                <a
+                                                    href='https://github.com/songquanpeng/one-api'
+                                                    target='_blank'
+                                                >
+                                                    https://github.com/songquanpeng/one-api
+                                                </a>
+                                            </p>
+                                            <p>启动时间:{getStartTimeString()}</p>
                                         </Card.Description>
                                     </Card.Content>
                                 </Card>
@@ -74,26 +83,32 @@ const Home = () => {
                             <Grid.Column>
                                 <Card fluid>
                                     <Card.Content>
-                                        <Card.Header>GPT-4</Card.Header>
-                                        <Card.Meta>信息总览</Card.Meta>
+                                        <Card.Header>系统配置</Card.Header>
+                                        <Card.Meta>系统配置总览</Card.Meta>
                                         <Card.Description>
-                                          <p>通道:官方通道|低价通道</p>
                                             <p>
-                                                状态:
-                                                {statusState?.status?.stable_price===-1?
-                                                    <span style={{color:'red'}}>不&nbsp;&nbsp;&nbsp;可&nbsp;&nbsp;&nbsp;用</span>
-                                                    :
-                                                    <span style={{color:'green'}}>可&emsp;&emsp;用</span>
-                                                }
-                                                |
-                                                {statusState?.status?.normal_price===-1?
-                                                    <span style={{color:'red'}}>不&nbsp;&nbsp;&nbsp;可&nbsp;&nbsp;&nbsp;用</span>
-                                                    :
-                                                    <span style={{color:'green'}}>可&emsp;&emsp;用</span>
-                                                }
+                                                邮箱验证:
+                                                {statusState?.status?.email_verification === true
+                                                    ? '已启用'
+                                                    : '未启用'}
+                                            </p>
+                                            <p>
+                                                GitHub 身份验证:
+                                                {statusState?.status?.github_oauth === true
+                                                    ? '已启用'
+                                                    : '未启用'}
                                             </p>
                                             <p>
-                                              价格:{statusState?.status?.stable_price}R&nbsp;/&nbsp;刀|{statusState?.status?.normal_price}R&nbsp;/&nbsp;刀
+                                                微信身份验证:
+                                                {statusState?.status?.wechat_login === true
+                                                    ? '已启用'
+                                                    : '未启用'}
+                                            </p>
+                                            <p>
+                                                Turnstile 用户校验:
+                                                {statusState?.status?.turnstile_check === true
+                                                    ? '已启用'
+                                                    : '未启用'}
                                             </p>
                                         </Card.Description>
                                     </Card.Content>
@@ -101,21 +116,18 @@ const Home = () => {
                             </Grid.Column>
                         </Grid>
                     </Segment>
-                  {
-                    homePageContent.startsWith('https://') ? <iframe
-                        src={homePageContent}
-                        style={{ width: '100%', height: '100vh', border: 'none' }}
-                    /> : <div style={{ fontSize: 'larger' }} dangerouslySetInnerHTML={{ __html: homePageContent }}></div>
-                  }
+                </> : <>
+                    {
+                        homePageContent.startsWith('https://') ? <iframe
+                            src={homePageContent}
+                            style={{ width: '100%', height: '100vh', border: 'none' }}
+                        /> : <div style={{ fontSize: 'larger' }} dangerouslySetInnerHTML={{ __html: homePageContent }}></div>
+                    }
                 </>
-                //     :
-              //     <>
-
-                // </>
             }
 
         </>
     );
 };
 
-export default Home;
+export default Home;

+ 16 - 16
web/src/pages/TopUp/index.js

@@ -78,9 +78,9 @@ const TopUp = () => {
                     form.submit()
                     document.body.removeChild(form)
                 } else {
-                    showError(message);
+                    showError(data);
                     // setTopUpCount(parseInt(res.data.count));
-                    setAmount(parseInt(data));
+                    // setAmount(parseInt(data));
                 }
             } else {
                 showError(res);
@@ -160,9 +160,9 @@ const TopUp = () => {
                                     setRedemptionCode(e.target.value);
                                 }}
                             />
-                            {/*<Button color='green' onClick={openTopUpLink}>*/}
-                            {/*  获取兑换码*/}
-                            {/*</Button>*/}
+                            <Button color='green' onClick={openTopUpLink}>
+                              获取兑换码
+                            </Button>
                             <Button color='yellow' onClick={topUp} disabled={isSubmitting}>
                                 {isSubmitting ? '兑换中...' : '兑换'}
                             </Button>
@@ -179,7 +179,7 @@ const TopUp = () => {
                 </Grid>
             </Segment>
             <Segment>
-                <Header as='h3'>在线充值(最小10刀)</Header>
+                <Header as='h3'>在线充值</Header>
                 <Grid columns={2} stackable>
                     <Grid.Column>
                         <Form>
@@ -194,27 +194,27 @@ const TopUp = () => {
                                     await getAmount(e.target.value);
                                 }}
                             />
-                            <Form.Input
-                                placeholder='充值码,如果你没有充值码,可不填写'
-                                name='redemptionCount'
-                                value={topUpCode}
-                                onChange={(e) => {
-                                    setTopUpCode(e.target.value);
-                                }}
-                            />
+                            {/*<Form.Input*/}
+                            {/*    placeholder='充值码,如果你没有充值码,可不填写'*/}
+                            {/*    name='redemptionCount'*/}
+                            {/*    value={topUpCode}*/}
+                            {/*    onChange={(e) => {*/}
+                            {/*        setTopUpCode(e.target.value);*/}
+                            {/*    }}*/}
+                            {/*/>*/}
                             <Button color='blue' onClick={
                                 async () => {
                                     onlineTopUp('zfb')
                                 }
                             }>
-                                支付宝(最大2000元)
+                                支付宝
                             </Button>
                             <Button color='green' onClick={
                                 async () => {
                                     onlineTopUp('wx')
                                 }
                             }>
-                                微信(最大2000元)
+                                微信
                             </Button>
                         </Form>
                     </Grid.Column>