mirror of
https://github.com/songquanpeng/one-api.git
synced 2026-04-27 04:04:27 +08:00
Compare commits
2 Commits
v0.6.9-alp
...
7ce535500f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ce535500f | ||
|
|
204f2ae0c6 |
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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("尚未实现")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -5,5 +5,4 @@ var ModelList = []string{
|
|||||||
"hunyuan-standard",
|
"hunyuan-standard",
|
||||||
"hunyuan-standard-256K",
|
"hunyuan-standard-256K",
|
||||||
"hunyuan-pro",
|
"hunyuan-pro",
|
||||||
"hunyuan-vision",
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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'];
|
||||||
|
|||||||
@@ -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>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
1
web/default/src/config.js
Normal file
1
web/default/src/config.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export const BASE_URL = process.env.REACT_APP_BASE_URL || '';
|
||||||
@@ -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(
|
||||||
|
|||||||
@@ -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 />
|
||||||
|
|||||||
Reference in New Issue
Block a user