diff --git a/common/constants.go b/common/constants.go
index 97e8583..d63955e 100644
--- a/common/constants.go
+++ b/common/constants.go
@@ -213,6 +213,7 @@ const (
ChannelTypeDify = 37
ChannelTypeJina = 38
ChannelCloudflare = 39
+ ChannelTypeSiliconFlow = 40
ChannelTypeDummy // this one is only for count, do not add any channel after this
@@ -259,4 +260,5 @@ var ChannelBaseURLs = []string{
"", //37
"https://api.jina.ai", //38
"https://api.cloudflare.com", //39
+ "https://api.siliconflow.cn", //40
}
diff --git a/common/email-outlook-auth.go b/common/email-outlook-auth.go
index 723a10b..f6a71b8 100644
--- a/common/email-outlook-auth.go
+++ b/common/email-outlook-auth.go
@@ -3,6 +3,7 @@ package common
import (
"errors"
"net/smtp"
+ "strings"
)
type outlookAuth struct {
@@ -30,3 +31,10 @@ func (a *outlookAuth) Next(fromServer []byte, more bool) ([]byte, error) {
}
return nil, nil
}
+
+func isOutlookServer(server string) bool {
+ // 兼容多地区的outlook邮箱和ofb邮箱
+ // 其实应该加一个Option来区分是否用LOGIN的方式登录
+ // 先临时兼容一下
+ return strings.Contains(server, "outlook") || strings.Contains(server, "onmicrosoft")
+}
diff --git a/common/email.go b/common/email.go
index 62c9048..905f385 100644
--- a/common/email.go
+++ b/common/email.go
@@ -9,6 +9,11 @@ import (
"time"
)
+func generateMessageID() string {
+ domain := strings.Split(SMTPFrom, "@")[1]
+ return fmt.Sprintf("<%d.%s@%s>", time.Now().UnixNano(), GetRandomString(12), domain)
+}
+
func SendEmail(subject string, receiver string, content string) error {
if SMTPFrom == "" { // for compatibility
SMTPFrom = SMTPAccount
@@ -18,8 +23,9 @@ func SendEmail(subject string, receiver string, content string) error {
"From: %s<%s>\r\n"+
"Subject: %s\r\n"+
"Date: %s\r\n"+
+ "Message-ID: %s\r\n"+ // 添加 Message-ID 头
"Content-Type: text/html; charset=UTF-8\r\n\r\n%s\r\n",
- receiver, SystemName, SMTPFrom, encodedSubject, time.Now().Format(time.RFC1123Z), content))
+ receiver, SystemName, SMTPFrom, encodedSubject, time.Now().Format(time.RFC1123Z), generateMessageID(), content))
auth := smtp.PlainAuth("", SMTPAccount, SMTPToken, SMTPServer)
addr := fmt.Sprintf("%s:%d", SMTPServer, SMTPPort)
to := strings.Split(receiver, ";")
@@ -62,7 +68,7 @@ func SendEmail(subject string, receiver string, content string) error {
if err != nil {
return err
}
- } else if strings.HasSuffix(SMTPAccount, "outlook.com") {
+ } else if isOutlookServer(SMTPAccount) {
auth = LoginAuth(SMTPAccount, SMTPToken)
err = smtp.SendMail(addr, auth, SMTPAccount, to, mail)
} else {
diff --git a/common/model-ratio.go b/common/model-ratio.go
index 3bdd5f7..946ee46 100644
--- a/common/model-ratio.go
+++ b/common/model-ratio.go
@@ -23,10 +23,11 @@ const (
var defaultModelRatio = map[string]float64{
//"midjourney": 50,
- "gpt-4-gizmo-*": 15,
- "gpt-4-all": 15,
- "gpt-4o-all": 15,
- "gpt-4": 15,
+ "gpt-4-gizmo-*": 15,
+ "gpt-4o-gizmo-*": 2.5,
+ "gpt-4-all": 15,
+ "gpt-4o-all": 15,
+ "gpt-4": 15,
//"gpt-4-0314": 15, //deprecated
"gpt-4-0613": 15,
"gpt-4-32k": 30,
@@ -37,6 +38,7 @@ var defaultModelRatio = map[string]float64{
"gpt-4-turbo-preview": 5, // $0.01 / 1K tokens
"gpt-4-vision-preview": 5, // $0.01 / 1K tokens
"gpt-4-1106-vision-preview": 5, // $0.01 / 1K tokens
+ "chatgpt-4o-latest": 2.5, // $0.01 / 1K tokens
"gpt-4o": 2.5, // $0.01 / 1K tokens
"gpt-4o-2024-05-13": 2.5, // $0.01 / 1K tokens
"gpt-4o-2024-08-06": 1.25, // $0.01 / 1K tokens
@@ -186,8 +188,8 @@ var defaultModelPrice = map[string]float64{
}
var (
- modelPriceMap = make(map[string]float64)
- modelPriceMapMutex = sync.RWMutex{}
+ modelPriceMap map[string]float64 = nil
+ modelPriceMapMutex = sync.RWMutex{}
)
var (
modelRatioMap map[string]float64 = nil
@@ -196,8 +198,9 @@ var (
var CompletionRatio map[string]float64 = nil
var defaultCompletionRatio = map[string]float64{
- "gpt-4-gizmo-*": 2,
- "gpt-4-all": 2,
+ "gpt-4-gizmo-*": 2,
+ "gpt-4o-gizmo-*": 3,
+ "gpt-4-all": 2,
}
func GetModelPriceMap() map[string]float64 {
@@ -231,6 +234,9 @@ func GetModelPrice(name string, printErr bool) (float64, bool) {
if strings.HasPrefix(name, "gpt-4-gizmo") {
name = "gpt-4-gizmo-*"
}
+ if strings.HasPrefix(name, "gpt-4o-gizmo") {
+ name = "gpt-4o-gizmo-*"
+ }
price, ok := modelPriceMap[name]
if !ok {
if printErr {
@@ -311,6 +317,9 @@ func GetCompletionRatio(name string) float64 {
if strings.HasPrefix(name, "gpt-4-gizmo") {
name = "gpt-4-gizmo-*"
}
+ if strings.HasPrefix(name, "gpt-4o-gizmo") {
+ name = "gpt-4o-gizmo-*"
+ }
if strings.HasPrefix(name, "gpt-3.5") {
if name == "gpt-3.5-turbo" || strings.HasSuffix(name, "0125") {
// https://openai.com/blog/new-embedding-models-and-api-updates
@@ -334,6 +343,9 @@ func GetCompletionRatio(name string) float64 {
}
return 2
}
+ if name == "chatgpt-4o-latest" {
+ return 3
+ }
if strings.Contains(name, "claude-instant-1") {
return 3
} else if strings.Contains(name, "claude-2") {
diff --git a/controller/log.go b/controller/log.go
index 3902810..ca0e1ec 100644
--- a/controller/log.go
+++ b/controller/log.go
@@ -1,18 +1,19 @@
package controller
import (
- "github.com/gin-gonic/gin"
"net/http"
"one-api/common"
"one-api/model"
"strconv"
+
+ "github.com/gin-gonic/gin"
)
func GetAllLogs(c *gin.Context) {
p, _ := strconv.Atoi(c.Query("p"))
pageSize, _ := strconv.Atoi(c.Query("page_size"))
- if p < 0 {
- p = 0
+ if p < 1 {
+ p = 1
}
if pageSize < 0 {
pageSize = common.ItemsPerPage
@@ -24,7 +25,7 @@ func GetAllLogs(c *gin.Context) {
tokenName := c.Query("token_name")
modelName := c.Query("model_name")
channel, _ := strconv.Atoi(c.Query("channel"))
- logs, err := model.GetAllLogs(logType, startTimestamp, endTimestamp, modelName, username, tokenName, p*pageSize, pageSize, channel)
+ logs, total, err := model.GetAllLogs(logType, startTimestamp, endTimestamp, modelName, username, tokenName, (p-1)*pageSize, pageSize, channel)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
@@ -35,16 +36,20 @@ func GetAllLogs(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "",
- "data": logs,
+ "data": map[string]any{
+ "items": logs,
+ "total": total,
+ "page": p,
+ "page_size": pageSize,
+ },
})
- return
}
func GetUserLogs(c *gin.Context) {
p, _ := strconv.Atoi(c.Query("p"))
pageSize, _ := strconv.Atoi(c.Query("page_size"))
- if p < 0 {
- p = 0
+ if p < 1 {
+ p = 1
}
if pageSize < 0 {
pageSize = common.ItemsPerPage
@@ -58,7 +63,7 @@ func GetUserLogs(c *gin.Context) {
endTimestamp, _ := strconv.ParseInt(c.Query("end_timestamp"), 10, 64)
tokenName := c.Query("token_name")
modelName := c.Query("model_name")
- logs, err := model.GetUserLogs(userId, logType, startTimestamp, endTimestamp, modelName, tokenName, p*pageSize, pageSize)
+ logs, total, err := model.GetUserLogs(userId, logType, startTimestamp, endTimestamp, modelName, tokenName, (p-1)*pageSize, pageSize)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
@@ -69,7 +74,12 @@ func GetUserLogs(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "",
- "data": logs,
+ "data": map[string]any{
+ "items": logs,
+ "total": total,
+ "page": p,
+ "page_size": pageSize,
+ },
})
return
}
diff --git a/dto/rerank.go b/dto/rerank.go
index 0ee44b1..dfa7963 100644
--- a/dto/rerank.go
+++ b/dto/rerank.go
@@ -1,14 +1,17 @@
package dto
type RerankRequest struct {
- Documents []any `json:"documents"`
- Query string `json:"query"`
- Model string `json:"model"`
- TopN int `json:"top_n"`
+ Documents []any `json:"documents"`
+ Query string `json:"query"`
+ Model string `json:"model"`
+ TopN int `json:"top_n"`
+ ReturnDocuments bool `json:"return_documents,omitempty"`
+ MaxChunkPerDoc int `json:"max_chunk_per_doc,omitempty"`
+ OverLapTokens int `json:"overlap_tokens,omitempty"`
}
type RerankResponseDocument struct {
- Document any `json:"document"`
+ Document any `json:"document,omitempty"`
Index int `json:"index"`
RelevanceScore float64 `json:"relevance_score"`
}
diff --git a/main.go b/main.go
index efee772..c7d6942 100644
--- a/main.go
+++ b/main.go
@@ -42,6 +42,11 @@ func main() {
if err != nil {
common.FatalLog("failed to initialize database: " + err.Error())
}
+ // Initialize SQL Database
+ err = model.InitLogDB()
+ if err != nil {
+ common.FatalLog("failed to initialize database: " + err.Error())
+ }
defer func() {
err := model.CloseDB()
if err != nil {
diff --git a/model/cache.go b/model/cache.go
index 2977bb6..7f5411e 100644
--- a/model/cache.go
+++ b/model/cache.go
@@ -270,6 +270,9 @@ func CacheGetRandomSatisfiedChannel(group string, model string, retry int) (*Cha
if strings.HasPrefix(model, "gpt-4-gizmo") {
model = "gpt-4-gizmo-*"
}
+ if strings.HasPrefix(model, "gpt-4o-gizmo") {
+ model = "gpt-4o-gizmo-*"
+ }
// if memory cache is disabled, get channel directly from database
if !common.MemoryCacheEnabled {
diff --git a/model/log.go b/model/log.go
index 1076145..5493a05 100644
--- a/model/log.go
+++ b/model/log.go
@@ -3,11 +3,12 @@ package model
import (
"context"
"fmt"
- "github.com/bytedance/gopkg/util/gopool"
- "gorm.io/gorm"
"one-api/common"
"strings"
"time"
+
+ "github.com/bytedance/gopkg/util/gopool"
+ "gorm.io/gorm"
)
type Log struct {
@@ -38,7 +39,7 @@ const (
)
func GetLogByKey(key string) (logs []*Log, err error) {
- err = DB.Joins("left join tokens on tokens.id = logs.token_id").Where("tokens.key = ?", strings.TrimPrefix(key, "sk-")).Find(&logs).Error
+ err = LOG_DB.Joins("left join tokens on tokens.id = logs.token_id").Where("tokens.key = ?", strings.TrimPrefix(key, "sk-")).Find(&logs).Error
return logs, err
}
@@ -54,7 +55,7 @@ func RecordLog(userId int, logType int, content string) {
Type: logType,
Content: content,
}
- err := DB.Create(log).Error
+ err := LOG_DB.Create(log).Error
if err != nil {
common.SysError("failed to record log: " + err.Error())
}
@@ -84,7 +85,7 @@ func RecordConsumeLog(ctx context.Context, userId int, channelId int, promptToke
IsStream: isStream,
Other: otherStr,
}
- err := DB.Create(log).Error
+ err := LOG_DB.Create(log).Error
if err != nil {
common.LogError(ctx, "failed to record log: "+err.Error())
}
@@ -95,12 +96,12 @@ func RecordConsumeLog(ctx context.Context, userId int, channelId int, promptToke
}
}
-func GetAllLogs(logType int, startTimestamp int64, endTimestamp int64, modelName string, username string, tokenName string, startIdx int, num int, channel int) (logs []*Log, err error) {
+func GetAllLogs(logType int, startTimestamp int64, endTimestamp int64, modelName string, username string, tokenName string, startIdx int, num int, channel int) (logs []*Log, total int64, err error) {
var tx *gorm.DB
if logType == LogTypeUnknown {
- tx = DB
+ tx = LOG_DB
} else {
- tx = DB.Where("type = ?", logType)
+ tx = LOG_DB.Where("type = ?", logType)
}
if modelName != "" {
tx = tx.Where("model_name like ?", modelName)
@@ -120,16 +121,23 @@ func GetAllLogs(logType int, startTimestamp int64, endTimestamp int64, modelName
if channel != 0 {
tx = tx.Where("channel_id = ?", channel)
}
+ err = tx.Model(&Log{}).Count(&total).Error
+ if err != nil {
+ return nil, 0, err
+ }
err = tx.Order("id desc").Limit(num).Offset(startIdx).Find(&logs).Error
- return logs, err
+ if err != nil {
+ return nil, 0, err
+ }
+ return logs, total, err
}
-func GetUserLogs(userId int, logType int, startTimestamp int64, endTimestamp int64, modelName string, tokenName string, startIdx int, num int) (logs []*Log, err error) {
+func GetUserLogs(userId int, logType int, startTimestamp int64, endTimestamp int64, modelName string, tokenName string, startIdx int, num int) (logs []*Log, total int64, err error) {
var tx *gorm.DB
if logType == LogTypeUnknown {
- tx = DB.Where("user_id = ?", userId)
+ tx = LOG_DB.Where("user_id = ?", userId)
} else {
- tx = DB.Where("user_id = ? and type = ?", userId, logType)
+ tx = LOG_DB.Where("user_id = ? and type = ?", userId, logType)
}
if modelName != "" {
tx = tx.Where("model_name like ?", modelName)
@@ -143,6 +151,10 @@ func GetUserLogs(userId int, logType int, startTimestamp int64, endTimestamp int
if endTimestamp != 0 {
tx = tx.Where("created_at <= ?", endTimestamp)
}
+ err = tx.Model(&Log{}).Count(&total).Error
+ if err != nil {
+ return nil, 0, err
+ }
err = tx.Order("id desc").Limit(num).Offset(startIdx).Omit("id").Find(&logs).Error
for i := range logs {
var otherMap map[string]interface{}
@@ -153,16 +165,16 @@ func GetUserLogs(userId int, logType int, startTimestamp int64, endTimestamp int
}
logs[i].Other = common.MapToJsonStr(otherMap)
}
- return logs, err
+ return logs, total, err
}
func SearchAllLogs(keyword string) (logs []*Log, err error) {
- err = DB.Where("type = ? or content LIKE ?", keyword, keyword+"%").Order("id desc").Limit(common.MaxRecentItems).Find(&logs).Error
+ err = LOG_DB.Where("type = ? or content LIKE ?", keyword, keyword+"%").Order("id desc").Limit(common.MaxRecentItems).Find(&logs).Error
return logs, err
}
func SearchUserLogs(userId int, keyword string) (logs []*Log, err error) {
- err = DB.Where("user_id = ? and type = ?", userId, keyword).Order("id desc").Limit(common.MaxRecentItems).Omit("id").Find(&logs).Error
+ err = LOG_DB.Where("user_id = ? and type = ?", userId, keyword).Order("id desc").Limit(common.MaxRecentItems).Omit("id").Find(&logs).Error
return logs, err
}
@@ -173,10 +185,10 @@ type Stat struct {
}
func SumUsedQuota(logType int, startTimestamp int64, endTimestamp int64, modelName string, username string, tokenName string, channel int) (stat Stat) {
- tx := DB.Table("logs").Select("sum(quota) quota")
+ tx := LOG_DB.Table("logs").Select("sum(quota) quota")
// 为rpm和tpm创建单独的查询
- rpmTpmQuery := DB.Table("logs").Select("count(*) rpm, sum(prompt_tokens) + sum(completion_tokens) tpm")
+ rpmTpmQuery := LOG_DB.Table("logs").Select("count(*) rpm, sum(prompt_tokens) + sum(completion_tokens) tpm")
if username != "" {
tx = tx.Where("username = ?", username)
@@ -215,7 +227,7 @@ func SumUsedQuota(logType int, startTimestamp int64, endTimestamp int64, modelNa
}
func SumUsedToken(logType int, startTimestamp int64, endTimestamp int64, modelName string, username string, tokenName string) (token int) {
- tx := DB.Table("logs").Select("ifnull(sum(prompt_tokens),0) + ifnull(sum(completion_tokens),0)")
+ tx := LOG_DB.Table("logs").Select("ifnull(sum(prompt_tokens),0) + ifnull(sum(completion_tokens),0)")
if username != "" {
tx = tx.Where("username = ?", username)
}
@@ -236,6 +248,6 @@ func SumUsedToken(logType int, startTimestamp int64, endTimestamp int64, modelNa
}
func DeleteOldLog(targetTimestamp int64) (int64, error) {
- result := DB.Where("created_at < ?", targetTimestamp).Delete(&Log{})
+ result := LOG_DB.Where("created_at < ?", targetTimestamp).Delete(&Log{})
return result.RowsAffected, result.Error
}
diff --git a/model/main.go b/model/main.go
index a70f21b..01eb6c9 100644
--- a/model/main.go
+++ b/model/main.go
@@ -15,6 +15,8 @@ import (
var DB *gorm.DB
+var LOG_DB *gorm.DB
+
func createRootAccountIfNeed() error {
var user User
//if user.Status != common.UserStatusEnabled {
@@ -38,9 +40,9 @@ func createRootAccountIfNeed() error {
return nil
}
-func chooseDB() (*gorm.DB, error) {
- if os.Getenv("SQL_DSN") != "" {
- dsn := os.Getenv("SQL_DSN")
+func chooseDB(envName string) (*gorm.DB, error) {
+ dsn := os.Getenv(envName)
+ if dsn != "" {
if strings.HasPrefix(dsn, "postgres://") {
// Use PostgreSQL
common.SysLog("using PostgreSQL as database")
@@ -52,6 +54,13 @@ func chooseDB() (*gorm.DB, error) {
PrepareStmt: true, // precompile SQL
})
}
+ if strings.HasPrefix(dsn, "local") {
+ common.SysLog("SQL_DSN not set, using SQLite as database")
+ common.UsingSQLite = true
+ return gorm.Open(sqlite.Open(common.SQLitePath), &gorm.Config{
+ PrepareStmt: true, // precompile SQL
+ })
+ }
// Use MySQL
common.SysLog("using MySQL as database")
// check parseTime
@@ -76,7 +85,7 @@ func chooseDB() (*gorm.DB, error) {
}
func InitDB() (err error) {
- db, err := chooseDB()
+ db, err := chooseDB("SQL_DSN")
if err == nil {
if common.DebugEnabled {
db = db.Debug()
@@ -100,52 +109,7 @@ func InitDB() (err error) {
// _, _ = sqlDB.Exec("ALTER TABLE midjourneys MODIFY status VARCHAR(20);") // TODO: delete this line when most users have upgraded
//}
common.SysLog("database migration started")
- err = db.AutoMigrate(&Channel{})
- if err != nil {
- return err
- }
- err = db.AutoMigrate(&Token{})
- if err != nil {
- return err
- }
- err = db.AutoMigrate(&User{})
- if err != nil {
- return err
- }
- err = db.AutoMigrate(&Option{})
- if err != nil {
- return err
- }
- err = db.AutoMigrate(&Redemption{})
- if err != nil {
- return err
- }
- err = db.AutoMigrate(&Ability{})
- if err != nil {
- return err
- }
- err = db.AutoMigrate(&Log{})
- if err != nil {
- return err
- }
- err = db.AutoMigrate(&Midjourney{})
- if err != nil {
- return err
- }
- err = db.AutoMigrate(&TopUp{})
- if err != nil {
- return err
- }
- err = db.AutoMigrate(&QuotaData{})
- if err != nil {
- return err
- }
- err = db.AutoMigrate(&Task{})
- if err != nil {
- return err
- }
- common.SysLog("database migrated")
- err = createRootAccountIfNeed()
+ err = migrateDB()
return err
} else {
common.FatalLog(err)
@@ -153,8 +117,103 @@ func InitDB() (err error) {
return err
}
-func CloseDB() error {
- sqlDB, err := DB.DB()
+func InitLogDB() (err error) {
+ if os.Getenv("LOG_SQL_DSN") == "" {
+ LOG_DB = DB
+ return
+ }
+ db, err := chooseDB("LOG_SQL_DSN")
+ if err == nil {
+ if common.DebugEnabled {
+ db = db.Debug()
+ }
+ LOG_DB = db
+ sqlDB, err := LOG_DB.DB()
+ if err != nil {
+ return err
+ }
+ sqlDB.SetMaxIdleConns(common.GetEnvOrDefault("SQL_MAX_IDLE_CONNS", 100))
+ sqlDB.SetMaxOpenConns(common.GetEnvOrDefault("SQL_MAX_OPEN_CONNS", 1000))
+ sqlDB.SetConnMaxLifetime(time.Second * time.Duration(common.GetEnvOrDefault("SQL_MAX_LIFETIME", 60)))
+
+ if !common.IsMasterNode {
+ return nil
+ }
+ //if common.UsingMySQL {
+ // _, _ = sqlDB.Exec("DROP INDEX idx_channels_key ON channels;") // TODO: delete this line when most users have upgraded
+ // _, _ = sqlDB.Exec("ALTER TABLE midjourneys MODIFY action VARCHAR(40);") // TODO: delete this line when most users have upgraded
+ // _, _ = sqlDB.Exec("ALTER TABLE midjourneys MODIFY progress VARCHAR(30);") // TODO: delete this line when most users have upgraded
+ // _, _ = sqlDB.Exec("ALTER TABLE midjourneys MODIFY status VARCHAR(20);") // TODO: delete this line when most users have upgraded
+ //}
+ common.SysLog("database migration started")
+ err = migrateLOGDB()
+ return err
+ } else {
+ common.FatalLog(err)
+ }
+ return err
+}
+
+func migrateDB() error {
+ err := DB.AutoMigrate(&Channel{})
+ if err != nil {
+ return err
+ }
+ err = DB.AutoMigrate(&Token{})
+ if err != nil {
+ return err
+ }
+ err = DB.AutoMigrate(&User{})
+ if err != nil {
+ return err
+ }
+ err = DB.AutoMigrate(&Option{})
+ if err != nil {
+ return err
+ }
+ err = DB.AutoMigrate(&Redemption{})
+ if err != nil {
+ return err
+ }
+ err = DB.AutoMigrate(&Ability{})
+ if err != nil {
+ return err
+ }
+ err = DB.AutoMigrate(&Log{})
+ if err != nil {
+ return err
+ }
+ err = DB.AutoMigrate(&Midjourney{})
+ if err != nil {
+ return err
+ }
+ err = DB.AutoMigrate(&TopUp{})
+ if err != nil {
+ return err
+ }
+ err = DB.AutoMigrate(&QuotaData{})
+ if err != nil {
+ return err
+ }
+ err = DB.AutoMigrate(&Task{})
+ if err != nil {
+ return err
+ }
+ common.SysLog("database migrated")
+ err = createRootAccountIfNeed()
+ return err
+}
+
+func migrateLOGDB() error {
+ var err error
+ if err = LOG_DB.AutoMigrate(&Log{}); err != nil {
+ return err
+ }
+ return nil
+}
+
+func closeDB(db *gorm.DB) error {
+ sqlDB, err := db.DB()
if err != nil {
return err
}
@@ -162,6 +221,16 @@ func CloseDB() error {
return err
}
+func CloseDB() error {
+ if LOG_DB != DB {
+ err := closeDB(LOG_DB)
+ if err != nil {
+ return err
+ }
+ }
+ return closeDB(DB)
+}
+
var (
lastPingTime time.Time
pingMutex sync.Mutex
diff --git a/relay/channel/claude/dto.go b/relay/channel/claude/dto.go
index e2a898e..8f289e3 100644
--- a/relay/channel/claude/dto.go
+++ b/relay/channel/claude/dto.go
@@ -31,9 +31,9 @@ type ClaudeMessage struct {
}
type Tool struct {
- Name string `json:"name"`
- Description string `json:"description,omitempty"`
- InputSchema InputSchema `json:"input_schema"`
+ Name string `json:"name"`
+ Description string `json:"description,omitempty"`
+ InputSchema map[string]interface{} `json:"input_schema"`
}
type InputSchema struct {
diff --git a/relay/channel/claude/relay-claude.go b/relay/channel/claude/relay-claude.go
index 031f825..47abe31 100644
--- a/relay/channel/claude/relay-claude.go
+++ b/relay/channel/claude/relay-claude.go
@@ -63,15 +63,21 @@ func RequestOpenAI2ClaudeMessage(textRequest dto.GeneralOpenAIRequest) (*ClaudeR
for _, tool := range textRequest.Tools {
if params, ok := tool.Function.Parameters.(map[string]any); ok {
- claudeTools = append(claudeTools, Tool{
+ claudeTool := Tool{
Name: tool.Function.Name,
Description: tool.Function.Description,
- InputSchema: InputSchema{
- Type: params["type"].(string),
- Properties: params["properties"],
- Required: params["required"],
- },
- })
+ }
+ claudeTool.InputSchema = make(map[string]interface{})
+ claudeTool.InputSchema["type"] = params["type"].(string)
+ claudeTool.InputSchema["properties"] = params["properties"]
+ claudeTool.InputSchema["required"] = params["required"]
+ for s, a := range params {
+ if s == "type" || s == "properties" || s == "required" {
+ continue
+ }
+ claudeTool.InputSchema[s] = a
+ }
+ claudeTools = append(claudeTools, claudeTool)
}
}
diff --git a/relay/channel/openai/constant.go b/relay/channel/openai/constant.go
index 81eb93c..ac2d673 100644
--- a/relay/channel/openai/constant.go
+++ b/relay/channel/openai/constant.go
@@ -8,6 +8,7 @@ var ModelList = []string{
"gpt-4-32k", "gpt-4-32k-0613",
"gpt-4-turbo-preview", "gpt-4-turbo", "gpt-4-turbo-2024-04-09",
"gpt-4-vision-preview",
+ "chatgpt-4o-latest",
"gpt-4o", "gpt-4o-2024-05-13", "gpt-4o-2024-08-06",
"gpt-4o-mini", "gpt-4o-mini-2024-07-18",
"text-embedding-ada-002", "text-embedding-3-small", "text-embedding-3-large",
diff --git a/relay/channel/siliconflow/adaptor.go b/relay/channel/siliconflow/adaptor.go
new file mode 100644
index 0000000..1614905
--- /dev/null
+++ b/relay/channel/siliconflow/adaptor.go
@@ -0,0 +1,80 @@
+package siliconflow
+
+import (
+ "errors"
+ "fmt"
+ "github.com/gin-gonic/gin"
+ "io"
+ "net/http"
+ "one-api/dto"
+ "one-api/relay/channel"
+ "one-api/relay/channel/openai"
+ relaycommon "one-api/relay/common"
+ "one-api/relay/constant"
+)
+
+type Adaptor struct {
+}
+
+func (a *Adaptor) ConvertAudioRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.AudioRequest) (io.Reader, error) {
+ //TODO implement me
+ return nil, errors.New("not implemented")
+}
+
+func (a *Adaptor) ConvertImageRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.ImageRequest) (any, error) {
+ //TODO implement me
+ return nil, errors.New("not implemented")
+}
+
+func (a *Adaptor) Init(info *relaycommon.RelayInfo) {
+}
+
+func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
+ if info.RelayMode == constant.RelayModeRerank {
+ return fmt.Sprintf("%s/v1/rerank", info.BaseUrl), nil
+ } else if info.RelayMode == constant.RelayModeEmbeddings {
+ return fmt.Sprintf("%s/v1/embeddings ", info.BaseUrl), nil
+ } else if info.RelayMode == constant.RelayModeChatCompletions {
+ return fmt.Sprintf("%s/v1/chat/completions", info.BaseUrl), nil
+ }
+ return "", errors.New("invalid relay mode")
+}
+
+func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request, info *relaycommon.RelayInfo) error {
+ channel.SetupApiRequestHeader(info, c, req)
+ req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", info.ApiKey))
+ return nil
+}
+
+func (a *Adaptor) ConvertRequest(c *gin.Context, info *relaycommon.RelayInfo, request *dto.GeneralOpenAIRequest) (any, error) {
+ return request, nil
+}
+
+func (a *Adaptor) DoRequest(c *gin.Context, info *relaycommon.RelayInfo, requestBody io.Reader) (*http.Response, error) {
+ return channel.DoApiRequest(a, c, info, requestBody)
+}
+
+func (a *Adaptor) ConvertRerankRequest(c *gin.Context, relayMode int, request dto.RerankRequest) (any, error) {
+ return request, nil
+}
+
+func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo) (usage *dto.Usage, err *dto.OpenAIErrorWithStatusCode) {
+ if info.RelayMode == constant.RelayModeRerank {
+ err, usage = siliconflowRerankHandler(c, resp)
+ } else if info.RelayMode == constant.RelayModeChatCompletions {
+ if info.IsStream {
+ err, usage = openai.OaiStreamHandler(c, resp, info)
+ } else {
+ err, usage = openai.OpenaiHandler(c, resp, info.PromptTokens, info.UpstreamModelName)
+ }
+ }
+ return
+}
+
+func (a *Adaptor) GetModelList() []string {
+ return ModelList
+}
+
+func (a *Adaptor) GetChannelName() string {
+ return ChannelName
+}
diff --git a/relay/channel/siliconflow/constant.go b/relay/channel/siliconflow/constant.go
new file mode 100644
index 0000000..819f4aa
--- /dev/null
+++ b/relay/channel/siliconflow/constant.go
@@ -0,0 +1,51 @@
+package siliconflow
+
+var ModelList = []string{
+ "THUDM/glm-4-9b-chat",
+ //"stabilityai/stable-diffusion-xl-base-1.0",
+ //"TencentARC/PhotoMaker",
+ "InstantX/InstantID",
+ //"stabilityai/stable-diffusion-2-1",
+ //"stabilityai/sd-turbo",
+ //"stabilityai/sdxl-turbo",
+ "ByteDance/SDXL-Lightning",
+ "deepseek-ai/deepseek-llm-67b-chat",
+ "Qwen/Qwen1.5-14B-Chat",
+ "Qwen/Qwen1.5-7B-Chat",
+ "Qwen/Qwen1.5-110B-Chat",
+ "Qwen/Qwen1.5-32B-Chat",
+ "01-ai/Yi-1.5-6B-Chat",
+ "01-ai/Yi-1.5-9B-Chat-16K",
+ "01-ai/Yi-1.5-34B-Chat-16K",
+ "THUDM/chatglm3-6b",
+ "deepseek-ai/DeepSeek-V2-Chat",
+ "Qwen/Qwen2-72B-Instruct",
+ "Qwen/Qwen2-7B-Instruct",
+ "Qwen/Qwen2-57B-A14B-Instruct",
+ //"stabilityai/stable-diffusion-3-medium",
+ "deepseek-ai/DeepSeek-Coder-V2-Instruct",
+ "Qwen/Qwen2-1.5B-Instruct",
+ "internlm/internlm2_5-7b-chat",
+ "BAAI/bge-large-en-v1.5",
+ "BAAI/bge-large-zh-v1.5",
+ "Pro/Qwen/Qwen2-7B-Instruct",
+ "Pro/Qwen/Qwen2-1.5B-Instruct",
+ "Pro/Qwen/Qwen1.5-7B-Chat",
+ "Pro/THUDM/glm-4-9b-chat",
+ "Pro/THUDM/chatglm3-6b",
+ "Pro/01-ai/Yi-1.5-9B-Chat-16K",
+ "Pro/01-ai/Yi-1.5-6B-Chat",
+ "Pro/google/gemma-2-9b-it",
+ "Pro/internlm/internlm2_5-7b-chat",
+ "Pro/meta-llama/Meta-Llama-3-8B-Instruct",
+ "Pro/mistralai/Mistral-7B-Instruct-v0.2",
+ "black-forest-labs/FLUX.1-schnell",
+ "iic/SenseVoiceSmall",
+ "netease-youdao/bce-embedding-base_v1",
+ "BAAI/bge-m3",
+ "internlm/internlm2_5-20b-chat",
+ "Qwen/Qwen2-Math-72B-Instruct",
+ "netease-youdao/bce-reranker-base_v1",
+ "BAAI/bge-reranker-v2-m3",
+}
+var ChannelName = "siliconflow"
diff --git a/relay/channel/siliconflow/dto.go b/relay/channel/siliconflow/dto.go
new file mode 100644
index 0000000..58cf81c
--- /dev/null
+++ b/relay/channel/siliconflow/dto.go
@@ -0,0 +1,17 @@
+package siliconflow
+
+import "one-api/dto"
+
+type SFTokens struct {
+ InputTokens int `json:"input_tokens"`
+ OutputTokens int `json:"output_tokens"`
+}
+
+type SFMeta struct {
+ Tokens SFTokens `json:"tokens"`
+}
+
+type SFRerankResponse struct {
+ Results []dto.RerankResponseDocument `json:"results"`
+ Meta SFMeta `json:"meta"`
+}
diff --git a/relay/channel/siliconflow/relay-siliconflow.go b/relay/channel/siliconflow/relay-siliconflow.go
new file mode 100644
index 0000000..a01e745
--- /dev/null
+++ b/relay/channel/siliconflow/relay-siliconflow.go
@@ -0,0 +1,44 @@
+package siliconflow
+
+import (
+ "encoding/json"
+ "github.com/gin-gonic/gin"
+ "io"
+ "net/http"
+ "one-api/dto"
+ "one-api/service"
+)
+
+func siliconflowRerankHandler(c *gin.Context, resp *http.Response) (*dto.OpenAIErrorWithStatusCode, *dto.Usage) {
+ responseBody, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return service.OpenAIErrorWrapper(err, "read_response_body_failed", http.StatusInternalServerError), nil
+ }
+ err = resp.Body.Close()
+ if err != nil {
+ return service.OpenAIErrorWrapper(err, "close_response_body_failed", http.StatusInternalServerError), nil
+ }
+ var siliconflowResp SFRerankResponse
+ err = json.Unmarshal(responseBody, &siliconflowResp)
+ if err != nil {
+ return service.OpenAIErrorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError), nil
+ }
+ usage := &dto.Usage{
+ PromptTokens: siliconflowResp.Meta.Tokens.InputTokens,
+ CompletionTokens: siliconflowResp.Meta.Tokens.OutputTokens,
+ TotalTokens: siliconflowResp.Meta.Tokens.InputTokens + siliconflowResp.Meta.Tokens.OutputTokens,
+ }
+ rerankResp := &dto.RerankResponse{
+ Results: siliconflowResp.Results,
+ Usage: *usage,
+ }
+
+ jsonResponse, err := json.Marshal(rerankResp)
+ if err != nil {
+ return service.OpenAIErrorWrapper(err, "marshal_response_body_failed", http.StatusInternalServerError), nil
+ }
+ c.Writer.Header().Set("Content-Type", "application/json")
+ c.Writer.WriteHeader(resp.StatusCode)
+ _, err = c.Writer.Write(jsonResponse)
+ return nil, usage
+}
diff --git a/relay/constant/api_type.go b/relay/constant/api_type.go
index 6bd93c4..36a6b6b 100644
--- a/relay/constant/api_type.go
+++ b/relay/constant/api_type.go
@@ -23,6 +23,7 @@ const (
APITypeDify
APITypeJina
APITypeCloudflare
+ APITypeSiliconFlow
APITypeDummy // this one is only for count, do not add any channel after this
)
@@ -66,6 +67,8 @@ func ChannelType2APIType(channelType int) (int, bool) {
apiType = APITypeJina
case common.ChannelCloudflare:
apiType = APITypeCloudflare
+ case common.ChannelTypeSiliconFlow:
+ apiType = APITypeSiliconFlow
}
if apiType == -1 {
return APITypeOpenAI, false
diff --git a/relay/relay-text.go b/relay/relay-text.go
index 93d202d..3c5393a 100644
--- a/relay/relay-text.go
+++ b/relay/relay-text.go
@@ -317,7 +317,7 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, modelN
totalTokens := promptTokens + completionTokens
var logContent string
if !usePrice {
- logContent = fmt.Sprintf("模型倍率 %.2f,分组倍率 %.2f,补全倍率 %.2f", modelRatio, groupRatio, completionRatio)
+ logContent = fmt.Sprintf("模型倍率 %.2f,补全倍率 %.2f,分组倍率 %.2f", modelRatio, completionRatio, groupRatio)
} else {
logContent = fmt.Sprintf("模型价格 %.2f,分组倍率 %.2f", modelPrice, groupRatio)
}
@@ -354,6 +354,10 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, modelN
logModel = "gpt-4-gizmo-*"
logContent += fmt.Sprintf(",模型 %s", modelName)
}
+ if strings.HasPrefix(logModel, "gpt-4o-gizmo") {
+ logModel = "gpt-4o-gizmo-*"
+ logContent += fmt.Sprintf(",模型 %s", modelName)
+ }
if extraContent != "" {
logContent += ", " + extraContent
}
diff --git a/relay/relay_adaptor.go b/relay/relay_adaptor.go
index 4c0aef1..96fb737 100644
--- a/relay/relay_adaptor.go
+++ b/relay/relay_adaptor.go
@@ -16,6 +16,7 @@ import (
"one-api/relay/channel/openai"
"one-api/relay/channel/palm"
"one-api/relay/channel/perplexity"
+ "one-api/relay/channel/siliconflow"
"one-api/relay/channel/task/suno"
"one-api/relay/channel/tencent"
"one-api/relay/channel/xunfei"
@@ -62,6 +63,8 @@ func GetAdaptor(apiType int) channel.Adaptor {
return &jina.Adaptor{}
case constant.APITypeCloudflare:
return &cloudflare.Adaptor{}
+ case constant.APITypeSiliconFlow:
+ return &siliconflow.Adaptor{}
}
return nil
}
diff --git a/relay/relay_rerank.go b/relay/relay_rerank.go
index 9885fd3..4242155 100644
--- a/relay/relay_rerank.go
+++ b/relay/relay_rerank.go
@@ -38,6 +38,23 @@ func RerankHelper(c *gin.Context, relayMode int) *dto.OpenAIErrorWithStatusCode
if len(rerankRequest.Documents) == 0 {
return service.OpenAIErrorWrapperLocal(fmt.Errorf("documents is empty"), "invalid_documents", http.StatusBadRequest)
}
+
+ // map model name
+ modelMapping := c.GetString("model_mapping")
+ //isModelMapped := false
+ if modelMapping != "" && modelMapping != "{}" {
+ modelMap := make(map[string]string)
+ err := json.Unmarshal([]byte(modelMapping), &modelMap)
+ if err != nil {
+ return service.OpenAIErrorWrapperLocal(err, "unmarshal_model_mapping_failed", http.StatusInternalServerError)
+ }
+ if modelMap[rerankRequest.Model] != "" {
+ rerankRequest.Model = modelMap[rerankRequest.Model]
+ // set upstream model name
+ //isModelMapped = true
+ }
+ }
+
relayInfo.UpstreamModelName = rerankRequest.Model
modelPrice, success := common.GetModelPrice(rerankRequest.Model, false)
groupRatio := common.GetGroupRatio(relayInfo.Group)
diff --git a/service/error.go b/service/error.go
index 3410de8..c760134 100644
--- a/service/error.go
+++ b/service/error.go
@@ -28,13 +28,11 @@ func MidjourneyErrorWithStatusCodeWrapper(code int, desc string, statusCode int)
// OpenAIErrorWrapper wraps an error into an OpenAIErrorWithStatusCode
func OpenAIErrorWrapper(err error, code string, statusCode int) *dto.OpenAIErrorWithStatusCode {
text := err.Error()
- // 定义一个正则表达式匹配URL
- if strings.Contains(text, "Post") || strings.Contains(text, "dial") {
+ lowerText := strings.ToLower(text)
+ if strings.Contains(lowerText, "post") || strings.Contains(lowerText, "dial") || strings.Contains(lowerText, "http") {
common.SysLog(fmt.Sprintf("error: %s", text))
text = "请求上游地址失败"
}
- //避免暴露内部错误
-
openAIError := dto.OpenAIError{
Message: text,
Type: "new_api_error",
@@ -113,14 +111,12 @@ func TaskErrorWrapperLocal(err error, code string, statusCode int) *dto.TaskErro
func TaskErrorWrapper(err error, code string, statusCode int) *dto.TaskError {
text := err.Error()
-
- // 定义一个正则表达式匹配URL
- if strings.Contains(text, "Post") || strings.Contains(text, "dial") {
+ lowerText := strings.ToLower(text)
+ if strings.Contains(lowerText, "post") || strings.Contains(lowerText, "dial") || strings.Contains(lowerText, "http") {
common.SysLog(fmt.Sprintf("error: %s", text))
text = "请求上游地址失败"
}
//避免暴露内部错误
-
taskError := &dto.TaskError{
Code: code,
Message: text,
diff --git a/web/package.json b/web/package.json
index e814d65..3bf290f 100644
--- a/web/package.json
+++ b/web/package.json
@@ -50,7 +50,7 @@
]
},
"devDependencies": {
- "@so1ve/prettier-config": "^2.0.0",
+ "@so1ve/prettier-config": "^3.1.0",
"@vitejs/plugin-react": "^4.2.1",
"prettier": "^3.0.0",
"typescript": "4.4.2",
diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml
index d7ea662..fd26d58 100644
--- a/web/pnpm-lock.yaml
+++ b/web/pnpm-lock.yaml
@@ -13,10 +13,10 @@ importers:
version: 2.53.2(react@18.2.0)
'@douyinfe/semi-ui':
specifier: ^2.55.3
- version: 2.55.3(react-dom@18.2.0)(react@18.2.0)
+ version: 2.55.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@visactor/react-vchart':
specifier: ~1.8.8
- version: 1.8.11(react-dom@18.2.0)(react@18.2.0)
+ version: 1.8.11(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@visactor/vchart':
specifier: ~1.8.8
version: 1.8.11
@@ -49,26 +49,26 @@ importers:
version: 1.0.4
react-router-dom:
specifier: ^6.3.0
- version: 6.22.2(react-dom@18.2.0)(react@18.2.0)
+ version: 6.22.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react-telegram-login:
specifier: ^1.1.2
version: 1.1.2(react@18.2.0)
react-toastify:
specifier: ^9.0.8
- version: 9.1.3(react-dom@18.2.0)(react@18.2.0)
+ version: 9.1.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react-turnstile:
specifier: ^1.0.5
- version: 1.1.3(react-dom@18.2.0)(react@18.2.0)
+ version: 1.1.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
semantic-ui-offline:
specifier: ^2.5.0
version: 2.5.0
semantic-ui-react:
specifier: ^2.1.3
- version: 2.1.5(react-dom@18.2.0)(react@18.2.0)
+ version: 2.1.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
devDependencies:
'@so1ve/prettier-config':
- specifier: ^2.0.0
- version: 2.0.0(prettier@3.2.5)
+ specifier: ^3.1.0
+ version: 3.1.0(prettier@3.2.5)
'@vitejs/plugin-react':
specifier: ^4.2.1
version: 4.2.1(vite@5.2.5)
@@ -88,8 +88,8 @@ packages:
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
engines: {node: '>=6.0.0'}
- '@astrojs/compiler@1.8.2':
- resolution: {integrity: sha512-o/ObKgtMzl8SlpIdzaxFnt7SATKPxu4oIP/1NL+HDJRzxfJcAkOTAb/ZKMRyULbz4q+1t2/DAebs2Z1QairkZw==}
+ '@astrojs/compiler@2.10.2':
+ resolution: {integrity: sha512-bvH+v8AirwpRWCkYJEyWYdc5Cs/BjG2ZTxIJzttHilXgfKJAdW2496KsUQKzf5j2tOHtaHXKKn9hb9WZiBGpEg==}
'@babel/code-frame@7.23.5':
resolution: {integrity: sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==}
@@ -578,13 +578,13 @@ packages:
react: ^16.0.0 || ^17.0.0 || ^18.0.0
react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0
- '@so1ve/prettier-config@2.0.0':
- resolution: {integrity: sha512-s6qsH5Rf4Bl+J0LU9rKmSWe/rYRdsYw0ELyXhDDDqEaTWtah4NpHKJuVWARuKqj0TWLBeWmyWUoIH/Bkp/DHaw==}
+ '@so1ve/prettier-config@3.1.0':
+ resolution: {integrity: sha512-9GJ1yXKBC4DzqCTTaZoBf8zw7WWkVuXcccZt1Aqk4lj6ab/GiNUnjPGajUVYLjaqAEOKqM7jUSUfTjk2JTjCAg==}
peerDependencies:
prettier: ^3.0.0
- '@so1ve/prettier-plugin-toml@2.0.0':
- resolution: {integrity: sha512-GvuFdTqhs3qxbhKTiCXWMXITmNLSdndUp7ql1yJbzzWaGqAdb3UH+R+0ZhtAEctBSx90MWAWW3kkW/Iba02tCg==}
+ '@so1ve/prettier-plugin-toml@3.1.0':
+ resolution: {integrity: sha512-8WZAGjAVNIJlkfWL6wHKxlUuEBY45fdd5qY5bR/Z6r/txgzKXk/r9qi1DTwc17gi/WcNuRrcRugecRT+mWbIYg==}
peerDependencies:
prettier: ^3.0.0
@@ -1127,12 +1127,12 @@ packages:
resolution: {integrity: sha512-WxtodH/wWavfw3MR7yK/GrS4pASEQ+iSTkdtSxPJWvqzG55ir5nvbLt9rw5AOiEcqqPCRM92WCtR1rk3TG3JSQ==}
hasBin: true
- prettier-plugin-astro@0.13.0:
- resolution: {integrity: sha512-5HrJNnPmZqTUNoA97zn4gNQv9BgVhv+et03314WpQ9H9N8m2L9OSV798olwmG2YLXPl1iSstlJCR1zB3x5xG4g==}
+ prettier-plugin-astro@0.14.1:
+ resolution: {integrity: sha512-RiBETaaP9veVstE4vUwSIcdATj6dKmXljouXc/DDNwBSPTp8FRkLGDSGFClKsAFeeg+13SB0Z1JZvbD76bigJw==}
engines: {node: ^14.15.0 || >=16.0.0}
- prettier-plugin-curly-and-jsdoc@2.0.0:
- resolution: {integrity: sha512-uSjWOWmX8+yrCrfhJSI58ODqtX7lXx07M8JYeOC1hfRv+vCttfiDlZoM27mNChGitJNKI+pCBvMMBYh8JiV0HQ==}
+ prettier-plugin-curly-and-jsdoc@3.1.0:
+ resolution: {integrity: sha512-4QMOHnLlkP2jTRWS0MFH6j+cuOiXLvXOqCLKbtwwVd8PPyq8NenW5AAwfwqiTNHBQG/DmzViPphRrwgN0XkUVQ==}
peerDependencies:
prettier: ^3.0.0
@@ -1442,7 +1442,7 @@ snapshots:
'@jridgewell/gen-mapping': 0.3.5
'@jridgewell/trace-mapping': 0.3.24
- '@astrojs/compiler@1.8.2': {}
+ '@astrojs/compiler@2.10.2': {}
'@babel/code-frame@7.23.5':
dependencies:
@@ -1590,7 +1590,7 @@ snapshots:
react: 18.2.0
tslib: 2.6.2
- '@dnd-kit/core@6.1.0(react-dom@18.2.0)(react@18.2.0)':
+ '@dnd-kit/core@6.1.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@dnd-kit/accessibility': 3.1.0(react@18.2.0)
'@dnd-kit/utilities': 3.2.2(react@18.2.0)
@@ -1598,9 +1598,9 @@ snapshots:
react-dom: 18.2.0(react@18.2.0)
tslib: 2.6.2
- '@dnd-kit/sortable@7.0.2(@dnd-kit/core@6.1.0)(react@18.2.0)':
+ '@dnd-kit/sortable@7.0.2(@dnd-kit/core@6.1.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0)':
dependencies:
- '@dnd-kit/core': 6.1.0(react-dom@18.2.0)(react@18.2.0)
+ '@dnd-kit/core': 6.1.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@dnd-kit/utilities': 3.2.2(react@18.2.0)
react: 18.2.0
tslib: 2.6.2
@@ -1652,10 +1652,10 @@ snapshots:
dependencies:
glob: 7.2.3
- '@douyinfe/semi-ui@2.55.3(react-dom@18.2.0)(react@18.2.0)':
+ '@douyinfe/semi-ui@2.55.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
- '@dnd-kit/core': 6.1.0(react-dom@18.2.0)(react@18.2.0)
- '@dnd-kit/sortable': 7.0.2(@dnd-kit/core@6.1.0)(react@18.2.0)
+ '@dnd-kit/core': 6.1.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ '@dnd-kit/sortable': 7.0.2(@dnd-kit/core@6.1.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0)
'@dnd-kit/utilities': 3.2.2(react@18.2.0)
'@douyinfe/semi-animation': 2.55.3
'@douyinfe/semi-animation-react': 2.55.3
@@ -1673,8 +1673,8 @@ snapshots:
prop-types: 15.8.1
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
- react-resizable: 3.0.5(react-dom@18.2.0)(react@18.2.0)
- react-window: 1.8.10(react-dom@18.2.0)(react@18.2.0)
+ react-resizable: 3.0.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ react-window: 1.8.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
scroll-into-view-if-needed: 2.2.31
utility-types: 3.11.0
@@ -1747,13 +1747,13 @@ snapshots:
'@esbuild/win32-x64@0.20.2':
optional: true
- '@fluentui/react-component-event-listener@0.63.1(react-dom@18.2.0)(react@18.2.0)':
+ '@fluentui/react-component-event-listener@0.63.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@babel/runtime': 7.24.0
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
- '@fluentui/react-component-ref@0.63.1(react-dom@18.2.0)(react@18.2.0)':
+ '@fluentui/react-component-ref@0.63.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@babel/runtime': 7.24.0
react: 18.2.0
@@ -1871,22 +1871,22 @@ snapshots:
'@rollup/rollup-win32-x64-msvc@4.13.0':
optional: true
- '@semantic-ui-react/event-stack@3.1.3(react-dom@18.2.0)(react@18.2.0)':
+ '@semantic-ui-react/event-stack@3.1.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
exenv: 1.2.2
prop-types: 15.8.1
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
- '@so1ve/prettier-config@2.0.0(prettier@3.2.5)':
+ '@so1ve/prettier-config@3.1.0(prettier@3.2.5)':
dependencies:
- '@so1ve/prettier-plugin-toml': 2.0.0(prettier@3.2.5)
+ '@so1ve/prettier-plugin-toml': 3.1.0(prettier@3.2.5)
prettier: 3.2.5
- prettier-plugin-astro: 0.13.0
- prettier-plugin-curly-and-jsdoc: 2.0.0(prettier@3.2.5)
+ prettier-plugin-astro: 0.14.1
+ prettier-plugin-curly-and-jsdoc: 3.1.0(prettier@3.2.5)
prettier-plugin-pkgsort: 0.2.1(prettier@3.2.5)
- '@so1ve/prettier-plugin-toml@2.0.0(prettier@3.2.5)':
+ '@so1ve/prettier-plugin-toml@3.1.0(prettier@3.2.5)':
dependencies:
prettier: 3.2.5
@@ -1951,7 +1951,7 @@ snapshots:
'@types/parse-json@4.0.2': {}
- '@visactor/react-vchart@1.8.11(react-dom@18.2.0)(react@18.2.0)':
+ '@visactor/react-vchart@1.8.11(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@visactor/vchart': 1.8.11
'@visactor/vgrammar-core': 0.10.11
@@ -2528,13 +2528,13 @@ snapshots:
sort-object-keys: 1.1.3
sort-order: 1.1.2
- prettier-plugin-astro@0.13.0:
+ prettier-plugin-astro@0.14.1:
dependencies:
- '@astrojs/compiler': 1.8.2
+ '@astrojs/compiler': 2.10.2
prettier: 3.2.5
sass-formatter: 0.7.9
- prettier-plugin-curly-and-jsdoc@2.0.0(prettier@3.2.5):
+ prettier-plugin-curly-and-jsdoc@3.1.0(prettier@3.2.5):
dependencies:
prettier: 3.2.5
@@ -2559,7 +2559,7 @@ snapshots:
react: 18.2.0
scheduler: 0.23.0
- react-draggable@4.4.6(react-dom@18.2.0)(react@18.2.0):
+ react-draggable@4.4.6(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
clsx: 1.2.1
prop-types: 15.8.1
@@ -2581,7 +2581,7 @@ snapshots:
react-is@18.2.0: {}
- react-popper@2.3.0(@popperjs/core@2.11.8)(react-dom@18.2.0)(react@18.2.0):
+ react-popper@2.3.0(@popperjs/core@2.11.8)(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@popperjs/core': 2.11.8
react: 18.2.0
@@ -2591,15 +2591,15 @@ snapshots:
react-refresh@0.14.0: {}
- react-resizable@3.0.5(react-dom@18.2.0)(react@18.2.0):
+ react-resizable@3.0.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
prop-types: 15.8.1
react: 18.2.0
- react-draggable: 4.4.6(react-dom@18.2.0)(react@18.2.0)
+ react-draggable: 4.4.6(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
transitivePeerDependencies:
- react-dom
- react-router-dom@6.22.2(react-dom@18.2.0)(react@18.2.0):
+ react-router-dom@6.22.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@remix-run/router': 1.15.2
react: 18.2.0
@@ -2615,18 +2615,18 @@ snapshots:
dependencies:
react: 18.2.0
- react-toastify@9.1.3(react-dom@18.2.0)(react@18.2.0):
+ react-toastify@9.1.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
clsx: 1.2.1
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
- react-turnstile@1.1.3(react-dom@18.2.0)(react@18.2.0):
+ react-turnstile@1.1.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
- react-window@1.8.10(react-dom@18.2.0)(react@18.2.0):
+ react-window@1.8.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.24.0
memoize-one: 5.2.1
@@ -2708,13 +2708,13 @@ snapshots:
fs-extra: 4.0.3
jquery: 3.7.1
- semantic-ui-react@2.1.5(react-dom@18.2.0)(react@18.2.0):
+ semantic-ui-react@2.1.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.24.0
- '@fluentui/react-component-event-listener': 0.63.1(react-dom@18.2.0)(react@18.2.0)
- '@fluentui/react-component-ref': 0.63.1(react-dom@18.2.0)(react@18.2.0)
+ '@fluentui/react-component-event-listener': 0.63.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ '@fluentui/react-component-ref': 0.63.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@popperjs/core': 2.11.8
- '@semantic-ui-react/event-stack': 3.1.3(react-dom@18.2.0)(react@18.2.0)
+ '@semantic-ui-react/event-stack': 3.1.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
clsx: 1.2.1
keyboard-key: 1.1.0
lodash: 4.17.21
@@ -2723,7 +2723,7 @@ snapshots:
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
react-is: 18.2.0
- react-popper: 2.3.0(@popperjs/core@2.11.8)(react-dom@18.2.0)(react@18.2.0)
+ react-popper: 2.3.0(@popperjs/core@2.11.8)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
shallowequal: 1.1.0
semver@6.3.1: {}
diff --git a/web/src/components/LogsTable.js b/web/src/components/LogsTable.js
index 2557c4b..072f2ad 100644
--- a/web/src/components/LogsTable.js
+++ b/web/src/components/LogsTable.js
@@ -1,11 +1,12 @@
import React, { useEffect, useState } from 'react';
import {
API,
- copy, getTodayStartTimestamp,
+ copy,
+ getTodayStartTimestamp,
isAdmin,
showError,
showSuccess,
- timestamp2string
+ timestamp2string,
} from '../helpers';
import {
@@ -29,7 +30,7 @@ import {
stringToColor,
} from '../helpers/render';
import Paragraph from '@douyinfe/semi-ui/lib/es/typography/paragraph';
-import {getLogOther} from "../helpers/other.js";
+import { getLogOther } from '../helpers/other.js';
const { Header } = Layout;
@@ -144,27 +145,27 @@ function renderUseTime(type) {
function renderFirstUseTime(type) {
let time = parseFloat(type) / 1000.0;
- time = time.toFixed(1)
+ time = time.toFixed(1);
if (time < 3) {
return (
-