Compare commits

..

2 Commits

Author SHA1 Message Date
Bibo Hao
7ce535500f Merge 204f2ae0c6 into 99c8c77504 2024-09-21 23:25:20 +08:00
Bibo Hao
204f2ae0c6 feat: allow baseUrl 2024-08-06 23:54:49 +08:00
30 changed files with 90 additions and 144 deletions

View File

@@ -5,6 +5,7 @@ import (
"github.com/gin-contrib/static" "github.com/gin-contrib/static"
"io/fs" "io/fs"
"net/http" "net/http"
"strings"
) )
// Credit: https://github.com/gin-contrib/static/issues/19 // Credit: https://github.com/gin-contrib/static/issues/19
@@ -14,7 +15,8 @@ type embedFileSystem struct {
} }
func (e embedFileSystem) Exists(prefix string, path string) bool { func (e embedFileSystem) Exists(prefix string, path string) bool {
_, err := e.Open(path) relPath := strings.TrimPrefix(path, prefix)
_, err := e.Open(relPath)
return err == nil return err == nil
} }

View File

@@ -31,15 +31,15 @@ func UnmarshalBodyReusable(c *gin.Context, v any) error {
contentType := c.Request.Header.Get("Content-Type") contentType := c.Request.Header.Get("Content-Type")
if strings.HasPrefix(contentType, "application/json") { if strings.HasPrefix(contentType, "application/json") {
err = json.Unmarshal(requestBody, &v) err = json.Unmarshal(requestBody, &v)
c.Request.Body = io.NopCloser(bytes.NewBuffer(requestBody))
} else { } else {
c.Request.Body = io.NopCloser(bytes.NewBuffer(requestBody)) // skip for now
err = c.ShouldBind(&v) // TODO: someday non json request have variant model, we will need to implementation this
} }
if err != nil { if err != nil {
return err return err
} }
// Reset request body // Reset request body
c.Request.Body = io.NopCloser(bytes.NewBuffer(requestBody))
return nil return nil
} }

View File

@@ -81,26 +81,6 @@ type APGC2DGPTUsageResponse struct {
TotalUsed float64 `json:"total_used"` TotalUsed float64 `json:"total_used"`
} }
type SiliconFlowUsageResponse struct {
Code int `json:"code"`
Message string `json:"message"`
Status bool `json:"status"`
Data struct {
ID string `json:"id"`
Name string `json:"name"`
Image string `json:"image"`
Email string `json:"email"`
IsAdmin bool `json:"isAdmin"`
Balance string `json:"balance"`
Status string `json:"status"`
Introduction string `json:"introduction"`
Role string `json:"role"`
ChargeBalance string `json:"chargeBalance"`
TotalBalance string `json:"totalBalance"`
Category string `json:"category"`
} `json:"data"`
}
// GetAuthHeader get auth header // GetAuthHeader get auth header
func GetAuthHeader(token string) http.Header { func GetAuthHeader(token string) http.Header {
h := http.Header{} h := http.Header{}
@@ -223,28 +203,6 @@ func updateChannelAIGC2DBalance(channel *model.Channel) (float64, error) {
return response.TotalAvailable, nil return response.TotalAvailable, nil
} }
func updateChannelSiliconFlowBalance(channel *model.Channel) (float64, error) {
url := "https://api.siliconflow.cn/v1/user/info"
body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
if err != nil {
return 0, err
}
response := SiliconFlowUsageResponse{}
err = json.Unmarshal(body, &response)
if err != nil {
return 0, err
}
if response.Code != 20000 {
return 0, fmt.Errorf("code: %d, message: %s", response.Code, response.Message)
}
balance, err := strconv.ParseFloat(response.Data.Balance, 64)
if err != nil {
return 0, err
}
channel.UpdateBalance(balance)
return balance, nil
}
func updateChannelBalance(channel *model.Channel) (float64, error) { func updateChannelBalance(channel *model.Channel) (float64, error) {
baseURL := channeltype.ChannelBaseURLs[channel.Type] baseURL := channeltype.ChannelBaseURLs[channel.Type]
if channel.GetBaseURL() == "" { if channel.GetBaseURL() == "" {
@@ -269,8 +227,6 @@ func updateChannelBalance(channel *model.Channel) (float64, error) {
return updateChannelAPI2GPTBalance(channel) return updateChannelAPI2GPTBalance(channel)
case channeltype.AIGC2D: case channeltype.AIGC2D:
return updateChannelAIGC2DBalance(channel) return updateChannelAIGC2DBalance(channel)
case channeltype.SiliconFlow:
return updateChannelSiliconFlowBalance(channel)
default: default:
return 0, errors.New("尚未实现") return 0, errors.New("尚未实现")
} }

View File

@@ -12,7 +12,7 @@ import (
) )
type ModelRequest struct { type ModelRequest struct {
Model string `json:"model" form:"model"` Model string `json:"model"`
} }
func Distribute() func(c *gin.Context) { func Distribute() func(c *gin.Context) {

View File

@@ -30,7 +30,7 @@ type Token struct {
RemainQuota int64 `json:"remain_quota" gorm:"bigint;default:0"` RemainQuota int64 `json:"remain_quota" gorm:"bigint;default:0"`
UnlimitedQuota bool `json:"unlimited_quota" gorm:"default:false"` UnlimitedQuota bool `json:"unlimited_quota" gorm:"default:false"`
UsedQuota int64 `json:"used_quota" gorm:"bigint;default:0"` // used quota UsedQuota int64 `json:"used_quota" gorm:"bigint;default:0"` // used quota
Models *string `json:"models" gorm:"type:text"` // allowed models Models *string `json:"models" gorm:"default:''"` // allowed models
Subnet *string `json:"subnet" gorm:"default:''"` // allowed subnet Subnet *string `json:"subnet" gorm:"default:''"` // allowed subnet
} }
@@ -121,40 +121,30 @@ func GetTokenById(id int) (*Token, error) {
return &token, err return &token, err
} }
func (t *Token) Insert() error { func (token *Token) Insert() error {
var err error var err error
err = DB.Create(t).Error err = DB.Create(token).Error
return err return err
} }
// Update Make sure your token's fields is completed, because this will update non-zero values // Update Make sure your token's fields is completed, because this will update non-zero values
func (t *Token) Update() error { func (token *Token) Update() error {
var err error var err error
err = DB.Model(t).Select("name", "status", "expired_time", "remain_quota", "unlimited_quota", "models", "subnet").Updates(t).Error err = DB.Model(token).Select("name", "status", "expired_time", "remain_quota", "unlimited_quota", "models", "subnet").Updates(token).Error
return err return err
} }
func (t *Token) SelectUpdate() error { func (token *Token) SelectUpdate() error {
// This can update zero values // This can update zero values
return DB.Model(t).Select("accessed_time", "status").Updates(t).Error return DB.Model(token).Select("accessed_time", "status").Updates(token).Error
} }
func (t *Token) Delete() error { func (token *Token) Delete() error {
var err error var err error
err = DB.Delete(t).Error err = DB.Delete(token).Error
return err return err
} }
func (t *Token) GetModels() string {
if t == nil {
return ""
}
if t.Models == nil {
return ""
}
return *t.Models
}
func DeleteTokenById(id int, userId int) (err error) { func DeleteTokenById(id int, userId int) (err error) {
// Why we need userId here? In case user want to delete other's token. // Why we need userId here? In case user want to delete other's token.
if id == 0 || userId == 0 { if id == 0 || userId == 0 {

View File

@@ -55,8 +55,8 @@ func StreamHandler(c *gin.Context, resp *http.Response, relayMode int) (*model.E
render.StringData(c, data) // if error happened, pass the data to client render.StringData(c, data) // if error happened, pass the data to client
continue // just ignore the error continue // just ignore the error
} }
if len(streamResponse.Choices) == 0 && streamResponse.Usage == nil { if len(streamResponse.Choices) == 0 {
// but for empty choice and no usage, we should not pass it to client, this is for azure // but for empty choice, we should not pass it to client, this is for azure
continue // just ignore empty choice continue // just ignore empty choice
} }
render.StringData(c, data) render.StringData(c, data)

View File

@@ -5,5 +5,4 @@ var ModelList = []string{
"hunyuan-standard", "hunyuan-standard",
"hunyuan-standard-256K", "hunyuan-standard-256K",
"hunyuan-pro", "hunyuan-pro",
"hunyuan-vision",
} }

View File

@@ -5,7 +5,6 @@ var ModelList = []string{
"SparkDesk-v1.1", "SparkDesk-v1.1",
"SparkDesk-v2.1", "SparkDesk-v2.1",
"SparkDesk-v3.1", "SparkDesk-v3.1",
"SparkDesk-v3.1-128K",
"SparkDesk-v3.5", "SparkDesk-v3.5",
"SparkDesk-v4.0", "SparkDesk-v4.0",
} }

View File

@@ -272,9 +272,9 @@ func xunfeiMakeRequest(textRequest model.GeneralOpenAIRequest, domain, authUrl,
} }
func parseAPIVersionByModelName(modelName string) string { func parseAPIVersionByModelName(modelName string) string {
index := strings.IndexAny(modelName, "-") parts := strings.Split(modelName, "-")
if index != -1 { if len(parts) == 2 {
return modelName[index+1:] return parts[1]
} }
return "" return ""
} }
@@ -288,8 +288,6 @@ func apiVersion2domain(apiVersion string) string {
return "generalv2" return "generalv2"
case "v3.1": case "v3.1":
return "generalv3" return "generalv3"
case "v3.1-128K":
return "pro-128k"
case "v3.5": case "v3.5":
return "generalv3.5" return "generalv3.5"
case "v4.0": case "v4.0":
@@ -299,14 +297,7 @@ func apiVersion2domain(apiVersion string) string {
} }
func getXunfeiAuthUrl(apiVersion string, apiKey string, apiSecret string) (string, string) { func getXunfeiAuthUrl(apiVersion string, apiKey string, apiSecret string) (string, string) {
var authUrl string
domain := apiVersion2domain(apiVersion) domain := apiVersion2domain(apiVersion)
switch apiVersion { authUrl := buildXunfeiAuthUrl(fmt.Sprintf("wss://spark-api.xf-yun.com/%s/chat", apiVersion), apiKey, apiSecret)
case "v3.1-128K":
authUrl = buildXunfeiAuthUrl(fmt.Sprintf("wss://spark-api.xf-yun.com/%s/pro-128k", apiVersion), apiKey, apiSecret)
break
default:
authUrl = buildXunfeiAuthUrl(fmt.Sprintf("wss://spark-api.xf-yun.com/%s/chat", apiVersion), apiKey, apiSecret)
}
return domain, authUrl return domain, authUrl
} }

View File

@@ -128,7 +128,6 @@ var ModelRatio = map[string]float64{
"SparkDesk-v1.1": 1.2858, // ¥0.018 / 1k tokens "SparkDesk-v1.1": 1.2858, // ¥0.018 / 1k tokens
"SparkDesk-v2.1": 1.2858, // ¥0.018 / 1k tokens "SparkDesk-v2.1": 1.2858, // ¥0.018 / 1k tokens
"SparkDesk-v3.1": 1.2858, // ¥0.018 / 1k tokens "SparkDesk-v3.1": 1.2858, // ¥0.018 / 1k tokens
"SparkDesk-v3.1-128K": 1.2858, // ¥0.018 / 1k tokens
"SparkDesk-v3.5": 1.2858, // ¥0.018 / 1k tokens "SparkDesk-v3.5": 1.2858, // ¥0.018 / 1k tokens
"SparkDesk-v4.0": 1.2858, // ¥0.018 / 1k tokens "SparkDesk-v4.0": 1.2858, // ¥0.018 / 1k tokens
"360GPT_S2_V9": 0.8572, // ¥0.012 / 1k tokens "360GPT_S2_V9": 0.8572, // ¥0.012 / 1k tokens

View File

@@ -9,7 +9,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
func SetApiRouter(router *gin.Engine) { func SetApiRouter(router *gin.RouterGroup) {
apiRouter := router.Group("/api") apiRouter := router.Group("/api")
apiRouter.Use(gzip.Gzip(gzip.DefaultCompression)) apiRouter.Use(gzip.Gzip(gzip.DefaultCompression))
apiRouter.Use(middleware.GlobalAPIRateLimit()) apiRouter.Use(middleware.GlobalAPIRateLimit())

View File

@@ -7,7 +7,7 @@ import (
"github.com/songquanpeng/one-api/middleware" "github.com/songquanpeng/one-api/middleware"
) )
func SetDashboardRouter(router *gin.Engine) { func SetDashboardRouter(router *gin.RouterGroup) {
apiRouter := router.Group("/") apiRouter := router.Group("/")
apiRouter.Use(middleware.CORS()) apiRouter.Use(middleware.CORS())
apiRouter.Use(gzip.Gzip(gzip.DefaultCompression)) apiRouter.Use(gzip.Gzip(gzip.DefaultCompression))

View File

@@ -11,7 +11,13 @@ import (
"strings" "strings"
) )
func SetRouter(router *gin.Engine, buildFS embed.FS) { func SetRouter(engine *gin.Engine, buildFS embed.FS) {
var baseUrl = os.Getenv("BASE_URL")
if baseUrl == "" {
baseUrl = "/"
}
router := engine.Group(baseUrl)
SetApiRouter(router) SetApiRouter(router)
SetDashboardRouter(router) SetDashboardRouter(router)
SetRelayRouter(router) SetRelayRouter(router)
@@ -21,10 +27,10 @@ func SetRouter(router *gin.Engine, buildFS embed.FS) {
logger.SysLog("FRONTEND_BASE_URL is ignored on master node") logger.SysLog("FRONTEND_BASE_URL is ignored on master node")
} }
if frontendBaseUrl == "" { if frontendBaseUrl == "" {
SetWebRouter(router, buildFS) SetWebRouter(engine, baseUrl, buildFS)
} else { } else {
frontendBaseUrl = strings.TrimSuffix(frontendBaseUrl, "/") frontendBaseUrl = strings.TrimSuffix(frontendBaseUrl, "/")
router.NoRoute(func(c *gin.Context) { engine.NoRoute(func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, fmt.Sprintf("%s%s", frontendBaseUrl, c.Request.RequestURI)) c.Redirect(http.StatusMovedPermanently, fmt.Sprintf("%s%s", frontendBaseUrl, c.Request.RequestURI))
}) })
} }

View File

@@ -7,7 +7,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
func SetRelayRouter(router *gin.Engine) { func SetRelayRouter(router *gin.RouterGroup) {
router.Use(middleware.CORS()) router.Use(middleware.CORS())
// https://platform.openai.com/docs/api-reference/introduction // https://platform.openai.com/docs/api-reference/introduction
modelsRouter := router.Group("/v1/models") modelsRouter := router.Group("/v1/models")

View File

@@ -14,13 +14,15 @@ import (
"strings" "strings"
) )
func SetWebRouter(router *gin.Engine, buildFS embed.FS) { func SetWebRouter(engine *gin.Engine, baseUrl string, buildFS embed.FS) {
indexPageData, _ := buildFS.ReadFile(fmt.Sprintf("web/build/%s/index.html", config.Theme)) basePath := fmt.Sprintf("web/build/%s", config.Theme)
router.Use(gzip.Gzip(gzip.DefaultCompression)) indexPageData, _ := buildFS.ReadFile(fmt.Sprintf("%s/index.html", basePath))
router.Use(middleware.GlobalWebRateLimit()) engine.Use(gzip.Gzip(gzip.DefaultCompression))
router.Use(middleware.Cache()) engine.Use(middleware.GlobalWebRateLimit())
router.Use(static.Serve("/", common.EmbedFolder(buildFS, fmt.Sprintf("web/build/%s", config.Theme)))) engine.Use(middleware.Cache())
router.NoRoute(func(c *gin.Context) { engine.Use(static.Serve(baseUrl, common.EmbedFolder(buildFS, basePath)))
engine.NoRoute(func(c *gin.Context) {
if strings.HasPrefix(c.Request.RequestURI, "/v1") || strings.HasPrefix(c.Request.RequestURI, "/api") { if strings.HasPrefix(c.Request.RequestURI, "/v1") || strings.HasPrefix(c.Request.RequestURI, "/api") {
controller.RelayNotFound(c) controller.RelayNotFound(c)
return return

View File

@@ -78,7 +78,7 @@ const EditChannel = (props) => {
localModels = ['chatglm_pro', 'chatglm_std', 'chatglm_lite']; localModels = ['chatglm_pro', 'chatglm_std', 'chatglm_lite'];
break; break;
case 18: case 18:
localModels = ['SparkDesk', 'SparkDesk-v1.1', 'SparkDesk-v2.1', 'SparkDesk-v3.1', 'SparkDesk-v3.1-128K', 'SparkDesk-v3.5', 'SparkDesk-v4.0']; localModels = ['SparkDesk', 'SparkDesk-v1.1', 'SparkDesk-v2.1', 'SparkDesk-v3.1', 'SparkDesk-v3.5', 'SparkDesk-v4.0'];
break; break;
case 19: case 19:
localModels = ['360GPT_S2_V9', 'embedding-bert-512-v1', 'embedding_s1_v1', 'semantic_similarity_s1_v1']; localModels = ['360GPT_S2_V9', 'embedding-bert-512-v1', 'embedding_s1_v1', 'semantic_similarity_s1_v1'];

View File

@@ -268,8 +268,6 @@ function renderBalance(type, balance) {
return <span>¥{balance.toFixed(2)}</span>; return <span>¥{balance.toFixed(2)}</span>;
case 13: // AIGC2D case 13: // AIGC2D
return <span>{renderNumber(balance)}</span>; return <span>{renderNumber(balance)}</span>;
case 44: // SiliconFlow
return <span>¥{balance.toFixed(2)}</span>;
default: default:
return <span>不支持</span>; return <span>不支持</span>;
} }

View File

@@ -91,7 +91,7 @@ const typeConfig = {
other: '版本号' other: '版本号'
}, },
input: { input: {
models: ['SparkDesk', 'SparkDesk-v1.1', 'SparkDesk-v2.1', 'SparkDesk-v3.1', 'SparkDesk-v3.1-128K', 'SparkDesk-v3.5', 'SparkDesk-v4.0'] models: ['SparkDesk', 'SparkDesk-v1.1', 'SparkDesk-v2.1', 'SparkDesk-v3.1', 'SparkDesk-v3.5', 'SparkDesk-v4.0']
}, },
prompt: { prompt: {
key: '按照如下格式输入APPID|APISecret|APIKey', key: '按照如下格式输入APPID|APISecret|APIKey',

View File

@@ -8,6 +8,7 @@ while IFS= read -r theme; do
rm -r build/$theme rm -r build/$theme
cd "$theme" cd "$theme"
npm install npm install
jq ".homepage=\"${REACT_APP_BASE_URL}\"" package.json > tmp.json && mv tmp.json package.json ;
DISABLE_ESLINT_PLUGIN='true' REACT_APP_VERSION=$version npm run build DISABLE_ESLINT_PLUGIN='true' REACT_APP_VERSION=$version npm run build
cd .. cd ..
done < THEMES done < THEMES

View File

@@ -1,6 +1,7 @@
{ {
"name": "react-template", "name": "react-template",
"version": "0.1.0", "version": "0.1.0",
"homepage": "",
"private": true, "private": true,
"dependencies": { "dependencies": {
"axios": "^0.27.2", "axios": "^0.27.2",

View File

@@ -2,7 +2,7 @@
<html lang="zh-CN"> <html lang="zh-CN">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="icon" href="logo.png" /> <link id="favicon" rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#ffffff" /> <meta name="theme-color" content="#ffffff" />
<meta <meta

View File

@@ -52,8 +52,6 @@ function renderBalance(type, balance) {
return <span>¥{balance.toFixed(2)}</span>; return <span>¥{balance.toFixed(2)}</span>;
case 13: // AIGC2D case 13: // AIGC2D
return <span>{renderNumber(balance)}</span>; return <span>{renderNumber(balance)}</span>;
case 44: // SiliconFlow
return <span>¥{balance.toFixed(2)}</span>;
default: default:
return <span>不支持</span>; return <span>不支持</span>;
} }

View File

@@ -1,7 +1,7 @@
import React, { useContext, useState } from 'react'; import React, { useContext, useState } from 'react';
import { Link, useNavigate } from 'react-router-dom'; import { Link, useNavigate } from 'react-router-dom';
import { UserContext } from '../context/User'; import { UserContext } from '../context/User';
import { BASE_URL } from "../config";
import { Button, Container, Dropdown, Icon, Menu, Segment } from 'semantic-ui-react'; import { Button, Container, Dropdown, Icon, Menu, Segment } from 'semantic-ui-react';
import { API, getLogo, getSystemName, isAdmin, isMobile, showSuccess } from '../helpers'; import { API, getLogo, getSystemName, isAdmin, isMobile, showSuccess } from '../helpers';
import '../index.css'; import '../index.css';
@@ -119,19 +119,14 @@ const Header = () => {
size='large' size='large'
style={ style={
showSidebar showSidebar
? { ? { borderBottom: 'none', marginBottom: '0', borderTop: 'none', height: '51px' }
borderBottom: 'none',
marginBottom: '0',
borderTop: 'none',
height: '51px'
}
: { borderTop: 'none', height: '52px' } : { borderTop: 'none', height: '52px' }
} }
> >
<Container> <Container>
<Menu.Item as={Link} to='/'> <Menu.Item as={Link} to='/'>
<img <img
src={logo} src={ BASE_URL + logo}
alt='logo' alt='logo'
style={{ marginRight: '0.75em' }} style={{ marginRight: '0.75em' }}
/> />
@@ -188,7 +183,7 @@ const Header = () => {
<Menu borderless style={{ borderTop: 'none' }}> <Menu borderless style={{ borderTop: 'none' }}>
<Container> <Container>
<Menu.Item as={Link} to='/' className={'hide-on-mobile'}> <Menu.Item as={Link} to='/' className={'hide-on-mobile'}>
<img src={logo} alt='logo' style={{ marginRight: '0.75em' }} /> <img src={ BASE_URL + logo} alt='logo' style={{ marginRight: '0.75em' }} />
<div style={{ fontSize: '20px' }}> <div style={{ fontSize: '20px' }}>
<b>{systemName}</b> <b>{systemName}</b>
</div> </div>

View File

@@ -4,6 +4,7 @@ import { Link, useNavigate, useSearchParams } from 'react-router-dom';
import { UserContext } from '../context/User'; import { UserContext } from '../context/User';
import { API, getLogo, showError, showSuccess, showWarning } from '../helpers'; import { API, getLogo, showError, showSuccess, showWarning } from '../helpers';
import { onGitHubOAuthClicked, onLarkOAuthClicked } from './utils'; import { onGitHubOAuthClicked, onLarkOAuthClicked } from './utils';
import { BASE_URL } from '../config';
import larkIcon from '../images/lark.svg'; import larkIcon from '../images/lark.svg';
const LoginForm = () => { const LoginForm = () => {
@@ -87,7 +88,7 @@ const LoginForm = () => {
<Grid textAlign='center' style={{ marginTop: '48px' }}> <Grid textAlign='center' style={{ marginTop: '48px' }}>
<Grid.Column style={{ maxWidth: 450 }}> <Grid.Column style={{ maxWidth: 450 }}>
<Header as='h2' color='' textAlign='center'> <Header as='h2' color='' textAlign='center'>
<Image src={logo} /> 用户登录 <Image src={ BASE_URL + logo} /> 用户登录
</Header> </Header>
<Form size='large'> <Form size='large'>
<Segment> <Segment>
@@ -151,13 +152,13 @@ const LoginForm = () => {
)} )}
{status.lark_client_id ? ( {status.lark_client_id ? (
<div style={{ <div style={{
background: "radial-gradient(circle, #FFFFFF, #FFFFFF, #00D6B9, #2F73FF, #0a3A9C)", background: "radial-gradient(circle, #FFFFFF, #FFFFFF, #00D6B9, #2F73FF, #0a3A9C)",
width: "36px", width: "36px",
height: "36px", height: "36px",
borderRadius: "10em", borderRadius: "10em",
display: "flex", display: "flex",
cursor: "pointer" cursor: "pointer"
}} }}
onClick={() => onLarkOAuthClicked(status.lark_client_id)} onClick={() => onLarkOAuthClicked(status.lark_client_id)}
> >
<Image <Image

View File

@@ -1,6 +1,7 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Button, Form, Grid, Header, Image, Segment } from 'semantic-ui-react'; import { Button, Form, Grid, Header, Image, Segment } from 'semantic-ui-react';
import { API, copy, showError, showInfo, showNotice, showSuccess } from '../helpers'; import { API, copy, showError, showInfo, showNotice, showSuccess } from '../helpers';
import { BASE_URL } from '../config';
import { useSearchParams } from 'react-router-dom'; import { useSearchParams } from 'react-router-dom';
const PasswordResetConfirm = () => { const PasswordResetConfirm = () => {
@@ -37,7 +38,7 @@ const PasswordResetConfirm = () => {
setDisableButton(false); setDisableButton(false);
setCountdown(30); setCountdown(30);
} }
return () => clearInterval(countdownInterval); return () => clearInterval(countdownInterval);
}, [disableButton, countdown]); }, [disableButton, countdown]);
async function handleSubmit(e) { async function handleSubmit(e) {
@@ -59,12 +60,12 @@ const PasswordResetConfirm = () => {
} }
setLoading(false); setLoading(false);
} }
return ( return (
<Grid textAlign='center' style={{ marginTop: '48px' }}> <Grid textAlign='center' style={{ marginTop: '48px' }}>
<Grid.Column style={{ maxWidth: 450 }}> <Grid.Column style={{ maxWidth: 450 }}>
<Header as='h2' color='' textAlign='center'> <Header as='h2' color='' textAlign='center'>
<Image src='/logo.png' /> 密码重置确认 <Image src={ BASE_URL + '/logo.png'} /> 密码重置确认
</Header> </Header>
<Form size='large'> <Form size='large'>
<Segment> <Segment>
@@ -79,19 +80,19 @@ const PasswordResetConfirm = () => {
/> />
{newPassword && ( {newPassword && (
<Form.Input <Form.Input
fluid fluid
icon='lock' icon='lock'
iconPosition='left' iconPosition='left'
placeholder='新密码' placeholder='新密码'
name='newPassword' name='newPassword'
value={newPassword} value={newPassword}
readOnly readOnly
onClick={(e) => { onClick={(e) => {
e.target.select(); e.target.select();
navigator.clipboard.writeText(newPassword); navigator.clipboard.writeText(newPassword);
showNotice(`密码已复制到剪贴板:${newPassword}`); showNotice(`密码已复制到剪贴板:${newPassword}`);
}} }}
/> />
)} )}
<Button <Button
color='green' color='green'
@@ -107,7 +108,7 @@ const PasswordResetConfirm = () => {
</Form> </Form>
</Grid.Column> </Grid.Column>
</Grid> </Grid>
); );
}; };
export default PasswordResetConfirm; export default PasswordResetConfirm;

View File

@@ -1,6 +1,7 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Button, Form, Grid, Header, Image, Segment } from 'semantic-ui-react'; import { Button, Form, Grid, Header, Image, Segment } from 'semantic-ui-react';
import { API, showError, showInfo, showSuccess } from '../helpers'; import { API, showError, showInfo, showSuccess } from '../helpers';
import { BASE_URL } from '../config';
import Turnstile from 'react-turnstile'; import Turnstile from 'react-turnstile';
const PasswordResetForm = () => { const PasswordResetForm = () => {
@@ -70,7 +71,7 @@ const PasswordResetForm = () => {
<Grid textAlign='center' style={{ marginTop: '48px' }}> <Grid textAlign='center' style={{ marginTop: '48px' }}>
<Grid.Column style={{ maxWidth: 450 }}> <Grid.Column style={{ maxWidth: 450 }}>
<Header as='h2' color='' textAlign='center'> <Header as='h2' color='' textAlign='center'>
<Image src='/logo.png' /> 密码重置 <Image src={ BASE_URL + '/logo.png'} /> 密码重置
</Header> </Header>
<Form size='large'> <Form size='large'>
<Segment> <Segment>

View File

@@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react';
import { Button, Form, Grid, Header, Image, Message, Segment } from 'semantic-ui-react'; import { Button, Form, Grid, Header, Image, Message, Segment } from 'semantic-ui-react';
import { Link, useNavigate } from 'react-router-dom'; import { Link, useNavigate } from 'react-router-dom';
import { API, getLogo, showError, showInfo, showSuccess } from '../helpers'; import { API, getLogo, showError, showInfo, showSuccess } from '../helpers';
import { BASE_URL } from '../config';
import Turnstile from 'react-turnstile'; import Turnstile from 'react-turnstile';
const RegisterForm = () => { const RegisterForm = () => {
@@ -101,7 +102,7 @@ const RegisterForm = () => {
<Grid textAlign='center' style={{ marginTop: '48px' }}> <Grid textAlign='center' style={{ marginTop: '48px' }}>
<Grid.Column style={{ maxWidth: 450 }}> <Grid.Column style={{ maxWidth: 450 }}>
<Header as='h2' color='' textAlign='center'> <Header as='h2' color='' textAlign='center'>
<Image src={logo} /> 新用户注册 <Image src={ BASE_URL + logo} /> 新用户注册
</Header> </Header>
<Form size='large'> <Form size='large'>
<Segment> <Segment>

View File

@@ -0,0 +1 @@
export const BASE_URL = process.env.REACT_APP_BASE_URL || '';

View File

@@ -1,8 +1,11 @@
import { showError } from './utils'; import { showError } from './utils';
import { BASE_URL } from '../config';
import axios from 'axios'; import axios from 'axios';
export const API = axios.create({ export const API = axios.create({
baseURL: process.env.REACT_APP_SERVER ? process.env.REACT_APP_SERVER : '', baseURL:
(process.env.REACT_APP_SERVER ? process.env.REACT_APP_SERVER : '') +
BASE_URL,
}); });
API.interceptors.response.use( API.interceptors.response.use(

View File

@@ -11,13 +11,14 @@ import { UserProvider } from './context/User';
import { ToastContainer } from 'react-toastify'; import { ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css'; import 'react-toastify/dist/ReactToastify.css';
import { StatusProvider } from './context/Status'; import { StatusProvider } from './context/Status';
import { BASE_URL } from './config';
const root = ReactDOM.createRoot(document.getElementById('root')); const root = ReactDOM.createRoot(document.getElementById('root'));
root.render( root.render(
<React.StrictMode> <React.StrictMode>
<StatusProvider> <StatusProvider>
<UserProvider> <UserProvider>
<BrowserRouter> <BrowserRouter basename={BASE_URL}>
<Header /> <Header />
<Container className={'main-content'}> <Container className={'main-content'}>
<App /> <App />