Merge branch 'main' into patch/gpt-4o-audio

This commit is contained in:
JustSong 2025-01-31 16:49:10 +08:00 committed by GitHub
commit 7d3e75a0b5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 392 additions and 233 deletions

View File

@ -7,19 +7,25 @@ import (
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
"runtime"
"strings"
"sync" "sync"
"time" "time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/songquanpeng/one-api/common/config" "github.com/songquanpeng/one-api/common/config"
"github.com/songquanpeng/one-api/common/helper" "github.com/songquanpeng/one-api/common/helper"
) )
type loggerLevel string
const ( const (
loggerDEBUG = "DEBUG" loggerDEBUG loggerLevel = "DEBUG"
loggerINFO = "INFO" loggerINFO loggerLevel = "INFO"
loggerWarn = "WARN" loggerWarn loggerLevel = "WARN"
loggerError = "ERR" loggerError loggerLevel = "ERROR"
loggerFatal loggerLevel = "FATAL"
) )
var setupLogOnce sync.Once var setupLogOnce sync.Once
@ -44,27 +50,26 @@ func SetupLogger() {
} }
func SysLog(s string) { func SysLog(s string) {
t := time.Now() logHelper(nil, loggerINFO, s)
_, _ = fmt.Fprintf(gin.DefaultWriter, "[SYS] %v | %s \n", t.Format("2006/01/02 - 15:04:05"), s)
} }
func SysLogf(format string, a ...any) { func SysLogf(format string, a ...any) {
SysLog(fmt.Sprintf(format, a...)) logHelper(nil, loggerINFO, fmt.Sprintf(format, a...))
} }
func SysError(s string) { func SysError(s string) {
t := time.Now() logHelper(nil, loggerError, s)
_, _ = fmt.Fprintf(gin.DefaultErrorWriter, "[SYS] %v | %s \n", t.Format("2006/01/02 - 15:04:05"), s)
} }
func SysErrorf(format string, a ...any) { func SysErrorf(format string, a ...any) {
SysError(fmt.Sprintf(format, a...)) logHelper(nil, loggerError, fmt.Sprintf(format, a...))
} }
func Debug(ctx context.Context, msg string) { func Debug(ctx context.Context, msg string) {
if config.DebugEnabled { if !config.DebugEnabled {
logHelper(ctx, loggerDEBUG, msg) return
} }
logHelper(ctx, loggerDEBUG, msg)
} }
func Info(ctx context.Context, msg string) { func Info(ctx context.Context, msg string) {
@ -80,37 +85,65 @@ func Error(ctx context.Context, msg string) {
} }
func Debugf(ctx context.Context, format string, a ...any) { func Debugf(ctx context.Context, format string, a ...any) {
Debug(ctx, fmt.Sprintf(format, a...)) logHelper(ctx, loggerDEBUG, fmt.Sprintf(format, a...))
} }
func Infof(ctx context.Context, format string, a ...any) { func Infof(ctx context.Context, format string, a ...any) {
Info(ctx, fmt.Sprintf(format, a...)) logHelper(ctx, loggerINFO, fmt.Sprintf(format, a...))
} }
func Warnf(ctx context.Context, format string, a ...any) { func Warnf(ctx context.Context, format string, a ...any) {
Warn(ctx, fmt.Sprintf(format, a...)) logHelper(ctx, loggerWarn, fmt.Sprintf(format, a...))
} }
func Errorf(ctx context.Context, format string, a ...any) { func Errorf(ctx context.Context, format string, a ...any) {
Error(ctx, fmt.Sprintf(format, a...)) logHelper(ctx, loggerError, fmt.Sprintf(format, a...))
} }
func logHelper(ctx context.Context, level string, msg string) { func FatalLog(s string) {
logHelper(nil, loggerFatal, s)
}
func FatalLogf(format string, a ...any) {
logHelper(nil, loggerFatal, fmt.Sprintf(format, a...))
}
func logHelper(ctx context.Context, level loggerLevel, msg string) {
writer := gin.DefaultErrorWriter writer := gin.DefaultErrorWriter
if level == loggerINFO { if level == loggerINFO {
writer = gin.DefaultWriter writer = gin.DefaultWriter
} }
id := ctx.Value(helper.RequestIdKey) var logId string
if id == nil { if ctx != nil {
id = helper.GenRequestID() rawLogId := ctx.Value(helper.RequestIdKey)
if rawLogId != nil {
logId = fmt.Sprintf(" | %s", rawLogId.(string))
}
} }
lineInfo, funcName := getLineInfo()
now := time.Now() now := time.Now()
_, _ = fmt.Fprintf(writer, "[%s] %v | %s | %s \n", level, now.Format("2006/01/02 - 15:04:05"), id, msg) _, _ = fmt.Fprintf(writer, "[%s] %v%s%s %s%s \n", level, now.Format("2006/01/02 - 15:04:05"), logId, lineInfo, funcName, msg)
SetupLogger() SetupLogger()
if level == loggerFatal {
os.Exit(1)
}
} }
func FatalLog(v ...any) { func getLineInfo() (string, string) {
t := time.Now() funcName := "[unknown] "
_, _ = fmt.Fprintf(gin.DefaultErrorWriter, "[FATAL] %v | %v \n", t.Format("2006/01/02 - 15:04:05"), v) pc, file, line, ok := runtime.Caller(3)
os.Exit(1) if ok {
if fn := runtime.FuncForPC(pc); fn != nil {
parts := strings.Split(fn.Name(), ".")
funcName = "[" + parts[len(parts)-1] + "] "
}
} else {
file = "unknown"
line = 0
}
parts := strings.Split(file, "one-api/")
if len(parts) > 1 {
file = parts[1]
}
return fmt.Sprintf(" | %s:%d", file, line), funcName
} }

View File

@ -7,7 +7,6 @@ import (
"net/http" "net/http"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/songquanpeng/one-api/common/config"
"github.com/songquanpeng/one-api/common/helper" "github.com/songquanpeng/one-api/common/helper"
channelhelper "github.com/songquanpeng/one-api/relay/adaptor" channelhelper "github.com/songquanpeng/one-api/relay/adaptor"
"github.com/songquanpeng/one-api/relay/adaptor/openai" "github.com/songquanpeng/one-api/relay/adaptor/openai"
@ -24,8 +23,11 @@ func (a *Adaptor) Init(meta *meta.Meta) {
} }
func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) { func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) {
defaultVersion := config.GeminiVersion var defaultVersion string
if meta.ActualModelName == "gemini-2.0-flash-exp" { switch meta.ActualModelName {
case "gemini-2.0-flash-exp",
"gemini-2.0-flash-thinking-exp",
"gemini-2.0-flash-thinking-exp-01-21":
defaultVersion = "v1beta" defaultVersion = "v1beta"
} }

View File

@ -7,5 +7,5 @@ var ModelList = []string{
"gemini-1.5-flash", "gemini-1.5-pro", "gemini-1.5-flash", "gemini-1.5-pro",
"text-embedding-004", "aqa", "text-embedding-004", "aqa",
"gemini-2.0-flash-exp", "gemini-2.0-flash-exp",
"gemini-2.0-flash-thinking-exp", "gemini-2.0-flash-thinking-exp", "gemini-2.0-flash-thinking-exp-01-21",
} }

View File

@ -2,16 +2,19 @@ package tencent
import ( import (
"errors" "errors"
"io"
"net/http"
"strconv"
"strings"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/songquanpeng/one-api/common/helper" "github.com/songquanpeng/one-api/common/helper"
"github.com/songquanpeng/one-api/relay/adaptor" "github.com/songquanpeng/one-api/relay/adaptor"
"github.com/songquanpeng/one-api/relay/adaptor/openai" "github.com/songquanpeng/one-api/relay/adaptor/openai"
"github.com/songquanpeng/one-api/relay/meta" "github.com/songquanpeng/one-api/relay/meta"
"github.com/songquanpeng/one-api/relay/model" "github.com/songquanpeng/one-api/relay/model"
"io" "github.com/songquanpeng/one-api/relay/relaymode"
"net/http"
"strconv"
"strings"
) )
// https://cloud.tencent.com/document/api/1729/101837 // https://cloud.tencent.com/document/api/1729/101837
@ -52,10 +55,18 @@ func (a *Adaptor) ConvertRequest(c *gin.Context, relayMode int, request *model.G
if err != nil { if err != nil {
return nil, err return nil, err
} }
tencentRequest := ConvertRequest(*request) var convertedRequest any
switch relayMode {
case relaymode.Embeddings:
a.Action = "GetEmbedding"
convertedRequest = ConvertEmbeddingRequest(*request)
default:
a.Action = "ChatCompletions"
convertedRequest = ConvertRequest(*request)
}
// we have to calculate the sign here // we have to calculate the sign here
a.Sign = GetSign(*tencentRequest, a, secretId, secretKey) a.Sign = GetSign(convertedRequest, a, secretId, secretKey)
return tencentRequest, nil return convertedRequest, nil
} }
func (a *Adaptor) ConvertImageRequest(request *model.ImageRequest) (any, error) { func (a *Adaptor) ConvertImageRequest(request *model.ImageRequest) (any, error) {
@ -75,7 +86,12 @@ func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, meta *meta.Met
err, responseText = StreamHandler(c, resp) err, responseText = StreamHandler(c, resp)
usage = openai.ResponseText2Usage(responseText, meta.ActualModelName, meta.PromptTokens) usage = openai.ResponseText2Usage(responseText, meta.ActualModelName, meta.PromptTokens)
} else { } else {
err, usage = Handler(c, resp) switch meta.Mode {
case relaymode.Embeddings:
err, usage = EmbeddingHandler(c, resp)
default:
err, usage = Handler(c, resp)
}
} }
return return
} }

View File

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

View File

@ -8,7 +8,6 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"github.com/songquanpeng/one-api/common/render"
"io" "io"
"net/http" "net/http"
"strconv" "strconv"
@ -16,11 +15,14 @@ import (
"time" "time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/common"
"github.com/songquanpeng/one-api/common/conv" "github.com/songquanpeng/one-api/common/conv"
"github.com/songquanpeng/one-api/common/ctxkey"
"github.com/songquanpeng/one-api/common/helper" "github.com/songquanpeng/one-api/common/helper"
"github.com/songquanpeng/one-api/common/logger" "github.com/songquanpeng/one-api/common/logger"
"github.com/songquanpeng/one-api/common/random" "github.com/songquanpeng/one-api/common/random"
"github.com/songquanpeng/one-api/common/render"
"github.com/songquanpeng/one-api/relay/adaptor/openai" "github.com/songquanpeng/one-api/relay/adaptor/openai"
"github.com/songquanpeng/one-api/relay/constant" "github.com/songquanpeng/one-api/relay/constant"
"github.com/songquanpeng/one-api/relay/model" "github.com/songquanpeng/one-api/relay/model"
@ -44,8 +46,68 @@ func ConvertRequest(request model.GeneralOpenAIRequest) *ChatRequest {
} }
} }
func ConvertEmbeddingRequest(request model.GeneralOpenAIRequest) *EmbeddingRequest {
return &EmbeddingRequest{
InputList: request.ParseInput(),
}
}
func EmbeddingHandler(c *gin.Context, resp *http.Response) (*model.ErrorWithStatusCode, *model.Usage) {
var tencentResponseP EmbeddingResponseP
err := json.NewDecoder(resp.Body).Decode(&tencentResponseP)
if err != nil {
return openai.ErrorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError), nil
}
err = resp.Body.Close()
if err != nil {
return openai.ErrorWrapper(err, "close_response_body_failed", http.StatusInternalServerError), nil
}
tencentResponse := tencentResponseP.Response
if tencentResponse.Error.Code != "" {
return &model.ErrorWithStatusCode{
Error: model.Error{
Message: tencentResponse.Error.Message,
Code: tencentResponse.Error.Code,
},
StatusCode: resp.StatusCode,
}, nil
}
requestModel := c.GetString(ctxkey.RequestModel)
fullTextResponse := embeddingResponseTencent2OpenAI(&tencentResponse)
fullTextResponse.Model = requestModel
jsonResponse, err := json.Marshal(fullTextResponse)
if err != nil {
return openai.ErrorWrapper(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, &fullTextResponse.Usage
}
func embeddingResponseTencent2OpenAI(response *EmbeddingResponse) *openai.EmbeddingResponse {
openAIEmbeddingResponse := openai.EmbeddingResponse{
Object: "list",
Data: make([]openai.EmbeddingResponseItem, 0, len(response.Data)),
Model: "hunyuan-embedding",
Usage: model.Usage{TotalTokens: response.EmbeddingUsage.TotalTokens},
}
for _, item := range response.Data {
openAIEmbeddingResponse.Data = append(openAIEmbeddingResponse.Data, openai.EmbeddingResponseItem{
Object: item.Object,
Index: item.Index,
Embedding: item.Embedding,
})
}
return &openAIEmbeddingResponse
}
func responseTencent2OpenAI(response *ChatResponse) *openai.TextResponse { func responseTencent2OpenAI(response *ChatResponse) *openai.TextResponse {
fullTextResponse := openai.TextResponse{ fullTextResponse := openai.TextResponse{
Id: response.ReqID,
Object: "chat.completion", Object: "chat.completion",
Created: helper.GetTimestamp(), Created: helper.GetTimestamp(),
Usage: model.Usage{ Usage: model.Usage{
@ -148,7 +210,7 @@ func Handler(c *gin.Context, resp *http.Response) (*model.ErrorWithStatusCode, *
return openai.ErrorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError), nil return openai.ErrorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError), nil
} }
TencentResponse = responseP.Response TencentResponse = responseP.Response
if TencentResponse.Error.Code != 0 { if TencentResponse.Error.Code != "" {
return &model.ErrorWithStatusCode{ return &model.ErrorWithStatusCode{
Error: model.Error{ Error: model.Error{
Message: TencentResponse.Error.Message, Message: TencentResponse.Error.Message,
@ -195,7 +257,7 @@ func hmacSha256(s, key string) string {
return string(hashed.Sum(nil)) return string(hashed.Sum(nil))
} }
func GetSign(req ChatRequest, adaptor *Adaptor, secId, secKey string) string { func GetSign(req any, adaptor *Adaptor, secId, secKey string) string {
// build canonical request string // build canonical request string
host := "hunyuan.tencentcloudapi.com" host := "hunyuan.tencentcloudapi.com"
httpRequestMethod := "POST" httpRequestMethod := "POST"

View File

@ -35,16 +35,16 @@ type ChatRequest struct {
// 1. 影响输出文本的多样性,取值越大,生成文本的多样性越强。 // 1. 影响输出文本的多样性,取值越大,生成文本的多样性越强。
// 2. 取值区间为 [0.0, 1.0],未传值时使用各模型推荐值。 // 2. 取值区间为 [0.0, 1.0],未传值时使用各模型推荐值。
// 3. 非必要不建议使用,不合理的取值会影响效果。 // 3. 非必要不建议使用,不合理的取值会影响效果。
TopP *float64 `json:"TopP"` TopP *float64 `json:"TopP,omitempty"`
// 说明: // 说明:
// 1. 较高的数值会使输出更加随机,而较低的数值会使其更加集中和确定。 // 1. 较高的数值会使输出更加随机,而较低的数值会使其更加集中和确定。
// 2. 取值区间为 [0.0, 2.0],未传值时使用各模型推荐值。 // 2. 取值区间为 [0.0, 2.0],未传值时使用各模型推荐值。
// 3. 非必要不建议使用,不合理的取值会影响效果。 // 3. 非必要不建议使用,不合理的取值会影响效果。
Temperature *float64 `json:"Temperature"` Temperature *float64 `json:"Temperature,omitempty"`
} }
type Error struct { type Error struct {
Code int `json:"Code"` Code string `json:"Code"`
Message string `json:"Message"` Message string `json:"Message"`
} }
@ -61,15 +61,41 @@ type ResponseChoices struct {
} }
type ChatResponse struct { type ChatResponse struct {
Choices []ResponseChoices `json:"Choices,omitempty"` // 结果 Choices []ResponseChoices `json:"Choices,omitempty"` // 结果
Created int64 `json:"Created,omitempty"` // unix 时间戳的字符串 Created int64 `json:"Created,omitempty"` // unix 时间戳的字符串
Id string `json:"Id,omitempty"` // 会话 id Id string `json:"Id,omitempty"` // 会话 id
Usage Usage `json:"Usage,omitempty"` // token 数量 Usage Usage `json:"Usage,omitempty"` // token 数量
Error Error `json:"Error,omitempty"` // 错误信息 注意:此字段可能返回 null表示取不到有效值 Error Error `json:"Error,omitempty"` // 错误信息 注意:此字段可能返回 null表示取不到有效值
Note string `json:"Note,omitempty"` // 注释 Note string `json:"Note,omitempty"` // 注释
ReqID string `json:"Req_id,omitempty"` // 唯一请求 Id每次请求都会返回。用于反馈接口入参 ReqID string `json:"RequestId,omitempty"` // 唯一请求 Id每次请求都会返回。用于反馈接口入参
} }
type ChatResponseP struct { type ChatResponseP struct {
Response ChatResponse `json:"Response,omitempty"` Response ChatResponse `json:"Response,omitempty"`
} }
type EmbeddingRequest struct {
InputList []string `json:"InputList"`
}
type EmbeddingData struct {
Embedding []float64 `json:"Embedding"`
Index int `json:"Index"`
Object string `json:"Object"`
}
type EmbeddingUsage struct {
PromptTokens int `json:"PromptTokens"`
TotalTokens int `json:"TotalTokens"`
}
type EmbeddingResponse struct {
Data []EmbeddingData `json:"Data"`
EmbeddingUsage EmbeddingUsage `json:"Usage,omitempty"`
RequestId string `json:"RequestId,omitempty"`
Error Error `json:"Error,omitempty"`
}
type EmbeddingResponseP struct {
Response EmbeddingResponse `json:"Response,omitempty"`
}

View File

@ -18,7 +18,8 @@ var ModelList = []string{
"gemini-pro", "gemini-pro-vision", "gemini-pro", "gemini-pro-vision",
"gemini-1.5-pro-001", "gemini-1.5-flash-001", "gemini-1.5-pro-001", "gemini-1.5-flash-001",
"gemini-1.5-pro-002", "gemini-1.5-flash-002", "gemini-1.5-pro-002", "gemini-1.5-flash-002",
"gemini-2.0-flash-exp", "gemini-2.0-flash-thinking-exp", "gemini-2.0-flash-exp",
"gemini-2.0-flash-thinking-exp", "gemini-2.0-flash-thinking-exp-01-21",
} }
type Adaptor struct { type Adaptor struct {

View File

@ -9,9 +9,10 @@ import (
) )
const ( const (
USD2RMB = 7 USD2RMB = 7
USD = 500 // $0.002 = 1 -> $1 = 500 USD = 500 // $0.002 = 1 -> $1 = 500
RMB = USD / USD2RMB MILLI_USD = 1.0 / 1000 * USD
RMB = USD / USD2RMB
) )
// ModelRatio // ModelRatio
@ -115,15 +116,16 @@ var ModelRatio = map[string]float64{
"bge-large-en": 0.002 * RMB, "bge-large-en": 0.002 * RMB,
"tao-8k": 0.002 * RMB, "tao-8k": 0.002 * RMB,
// https://ai.google.dev/pricing // https://ai.google.dev/pricing
"gemini-pro": 1, // $0.00025 / 1k characters -> $0.001 / 1k tokens "gemini-pro": 1, // $0.00025 / 1k characters -> $0.001 / 1k tokens
"gemini-1.0-pro": 1, "gemini-1.0-pro": 1,
"gemini-1.5-pro": 1, "gemini-1.5-pro": 1,
"gemini-1.5-pro-001": 1, "gemini-1.5-pro-001": 1,
"gemini-1.5-flash": 1, "gemini-1.5-flash": 1,
"gemini-1.5-flash-001": 1, "gemini-1.5-flash-001": 1,
"gemini-2.0-flash-exp": 1, "gemini-2.0-flash-exp": 1,
"gemini-2.0-flash-thinking-exp": 1, "gemini-2.0-flash-thinking-exp": 1,
"aqa": 1, "gemini-2.0-flash-thinking-exp-01-21": 1,
"aqa": 1,
// https://open.bigmodel.cn/pricing // https://open.bigmodel.cn/pricing
"glm-4": 0.1 * RMB, "glm-4": 0.1 * RMB,
"glm-4v": 0.1 * RMB, "glm-4v": 0.1 * RMB,
@ -284,8 +286,8 @@ var ModelRatio = map[string]float64{
"command-r": 0.5 / 1000 * USD, "command-r": 0.5 / 1000 * USD,
"command-r-plus": 3.0 / 1000 * USD, "command-r-plus": 3.0 / 1000 * USD,
// https://platform.deepseek.com/api-docs/pricing/ // https://platform.deepseek.com/api-docs/pricing/
"deepseek-chat": 1.0 / 1000 * RMB, "deepseek-chat": 0.14 * MILLI_USD,
"deepseek-coder": 1.0 / 1000 * RMB, "deepseek-reasoner": 0.55 * MILLI_USD,
// https://www.deepl.com/pro?cta=header-prices // https://www.deepl.com/pro?cta=header-prices
"deepl-zh": 25.0 / 1000 * USD, "deepl-zh": 25.0 / 1000 * USD,
"deepl-en": 25.0 / 1000 * USD, "deepl-en": 25.0 / 1000 * USD,
@ -407,6 +409,9 @@ var CompletionRatio = map[string]float64{
"llama3-70b-8192(33)": 0.0035 / 0.00265, "llama3-70b-8192(33)": 0.0035 / 0.00265,
// whisper // whisper
"whisper-1": 0, // only count input tokens "whisper-1": 0, // only count input tokens
// deepseek
"deepseek-chat": 0.28 / 0.14,
"deepseek-reasoner": 2.19 / 0.55,
} }
var ( var (

View File

@ -1,247 +1,260 @@
import { enqueueSnackbar } from 'notistack'; import {enqueueSnackbar} from 'notistack';
import { snackbarConstants } from 'constants/SnackbarConstants'; import {snackbarConstants} from 'constants/SnackbarConstants';
import { API } from './api'; import {API} from './api';
export function getSystemName() { export function getSystemName() {
let system_name = localStorage.getItem('system_name'); let system_name = localStorage.getItem('system_name');
if (!system_name) return 'One API'; if (!system_name) return 'One API';
return system_name; return system_name;
} }
export function isMobile() { export function isMobile() {
return window.innerWidth <= 600; return window.innerWidth <= 600;
} }
// eslint-disable-next-line // eslint-disable-next-line
export function SnackbarHTMLContent({ htmlContent }) { export function SnackbarHTMLContent({htmlContent}) {
return <div dangerouslySetInnerHTML={{ __html: htmlContent }} />; return <div dangerouslySetInnerHTML={{__html: htmlContent}}/>;
} }
export function getSnackbarOptions(variant) { export function getSnackbarOptions(variant) {
let options = snackbarConstants.Common[variant]; let options = snackbarConstants.Common[variant];
if (isMobile()) { if (isMobile()) {
// 合并 options 和 snackbarConstants.Mobile // 合并 options 和 snackbarConstants.Mobile
options = { ...options, ...snackbarConstants.Mobile }; options = {...options, ...snackbarConstants.Mobile};
} }
return options; return options;
} }
export function showError(error) { export function showError(error) {
if (error.message) { if (error.message) {
if (error.name === 'AxiosError') { if (error.name === 'AxiosError') {
switch (error.response.status) { switch (error.response.status) {
case 429: case 429:
enqueueSnackbar('错误:请求次数过多,请稍后再试!', getSnackbarOptions('ERROR')); enqueueSnackbar('错误:请求次数过多,请稍后再试!', getSnackbarOptions('ERROR'));
break; break;
case 500: case 500:
enqueueSnackbar('错误:服务器内部错误,请联系管理员!', getSnackbarOptions('ERROR')); enqueueSnackbar('错误:服务器内部错误,请联系管理员!', getSnackbarOptions('ERROR'));
break; break;
case 405: case 405:
enqueueSnackbar('本站仅作演示之用,无服务端!', getSnackbarOptions('INFO')); enqueueSnackbar('本站仅作演示之用,无服务端!', getSnackbarOptions('INFO'));
break; break;
default: default:
enqueueSnackbar('错误:' + error.message, getSnackbarOptions('ERROR')); enqueueSnackbar('错误:' + error.message, getSnackbarOptions('ERROR'));
} }
return; return;
}
} else {
enqueueSnackbar('错误:' + error, getSnackbarOptions('ERROR'));
} }
} else {
enqueueSnackbar('错误:' + error, getSnackbarOptions('ERROR'));
}
} }
export function showNotice(message, isHTML = false) { export function showNotice(message, isHTML = false) {
if (isHTML) { if (isHTML) {
enqueueSnackbar(<SnackbarHTMLContent htmlContent={message} />, getSnackbarOptions('NOTICE')); enqueueSnackbar(<SnackbarHTMLContent htmlContent={message}/>, getSnackbarOptions('NOTICE'));
} else { } else {
enqueueSnackbar(message, getSnackbarOptions('NOTICE')); enqueueSnackbar(message, getSnackbarOptions('NOTICE'));
} }
} }
export function showWarning(message) { export function showWarning(message) {
enqueueSnackbar(message, getSnackbarOptions('WARNING')); enqueueSnackbar(message, getSnackbarOptions('WARNING'));
} }
export function showSuccess(message) { export function showSuccess(message) {
enqueueSnackbar(message, getSnackbarOptions('SUCCESS')); enqueueSnackbar(message, getSnackbarOptions('SUCCESS'));
} }
export function showInfo(message) { export function showInfo(message) {
enqueueSnackbar(message, getSnackbarOptions('INFO')); enqueueSnackbar(message, getSnackbarOptions('INFO'));
} }
export async function getOAuthState() { export async function getOAuthState() {
const res = await API.get('/api/oauth/state'); const res = await API.get('/api/oauth/state');
const { success, message, data } = res.data; const {success, message, data} = res.data;
if (success) { if (success) {
return data; return data;
} else { } else {
showError(message); showError(message);
return ''; return '';
} }
} }
export async function onGitHubOAuthClicked(github_client_id, openInNewTab = false) { export async function onGitHubOAuthClicked(github_client_id, openInNewTab = false) {
const state = await getOAuthState(); const state = await getOAuthState();
if (!state) return; if (!state) return;
let url = `https://github.com/login/oauth/authorize?client_id=${github_client_id}&state=${state}&scope=user:email`; let url = `https://github.com/login/oauth/authorize?client_id=${github_client_id}&state=${state}&scope=user:email`;
if (openInNewTab) { if (openInNewTab) {
window.open(url); window.open(url);
} else { } else {
window.location.href = url; window.location.href = url;
} }
} }
export async function onLarkOAuthClicked(lark_client_id) { export async function onLarkOAuthClicked(lark_client_id) {
const state = await getOAuthState(); const state = await getOAuthState();
if (!state) return; if (!state) return;
let redirect_uri = `${window.location.origin}/oauth/lark`; let redirect_uri = `${window.location.origin}/oauth/lark`;
window.open(`https://accounts.feishu.cn/open-apis/authen/v1/authorize?redirect_uri=${redirect_uri}&client_id=${lark_client_id}&state=${state}`); window.open(`https://accounts.feishu.cn/open-apis/authen/v1/authorize?redirect_uri=${redirect_uri}&client_id=${lark_client_id}&state=${state}`);
} }
export async function onOidcClicked(auth_url, client_id, openInNewTab = false) { export async function onOidcClicked(auth_url, client_id, openInNewTab = false) {
const state = await getOAuthState(); const state = await getOAuthState();
if (!state) return; if (!state) return;
const redirect_uri = `${window.location.origin}/oauth/oidc`; const redirect_uri = `${window.location.origin}/oauth/oidc`;
const response_type = "code"; const response_type = "code";
const scope = "openid profile email"; const scope = "openid profile email";
const url = `${auth_url}?client_id=${client_id}&redirect_uri=${redirect_uri}&response_type=${response_type}&scope=${scope}&state=${state}`; const url = `${auth_url}?client_id=${client_id}&redirect_uri=${redirect_uri}&response_type=${response_type}&scope=${scope}&state=${state}`;
if (openInNewTab) { if (openInNewTab) {
window.open(url); window.open(url);
} else } else {
{ window.location.href = url;
window.location.href = url; }
}
} }
export function isAdmin() { export function isAdmin() {
let user = localStorage.getItem('user'); let user = localStorage.getItem('user');
if (!user) return false; if (!user) return false;
user = JSON.parse(user); user = JSON.parse(user);
return user.role >= 10; return user.role >= 10;
} }
export function timestamp2string(timestamp) { export function timestamp2string(timestamp) {
let date = new Date(timestamp * 1000); let date = new Date(timestamp * 1000);
let year = date.getFullYear().toString(); let year = date.getFullYear().toString();
let month = (date.getMonth() + 1).toString(); let month = (date.getMonth() + 1).toString();
let day = date.getDate().toString(); let day = date.getDate().toString();
let hour = date.getHours().toString(); let hour = date.getHours().toString();
let minute = date.getMinutes().toString(); let minute = date.getMinutes().toString();
let second = date.getSeconds().toString(); let second = date.getSeconds().toString();
if (month.length === 1) { if (month.length === 1) {
month = '0' + month; month = '0' + month;
} }
if (day.length === 1) { if (day.length === 1) {
day = '0' + day; day = '0' + day;
} }
if (hour.length === 1) { if (hour.length === 1) {
hour = '0' + hour; hour = '0' + hour;
} }
if (minute.length === 1) { if (minute.length === 1) {
minute = '0' + minute; minute = '0' + minute;
} }
if (second.length === 1) { if (second.length === 1) {
second = '0' + second; second = '0' + second;
} }
return year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second; return year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second;
} }
export function calculateQuota(quota, digits = 2) { export function calculateQuota(quota, digits = 2) {
let quotaPerUnit = localStorage.getItem('quota_per_unit'); let quotaPerUnit = localStorage.getItem('quota_per_unit');
quotaPerUnit = parseFloat(quotaPerUnit); quotaPerUnit = parseFloat(quotaPerUnit);
return (quota / quotaPerUnit).toFixed(digits); return (quota / quotaPerUnit).toFixed(digits);
} }
export function renderQuota(quota, digits = 2) { export function renderQuota(quota, digits = 2) {
let displayInCurrency = localStorage.getItem('display_in_currency'); let displayInCurrency = localStorage.getItem('display_in_currency');
displayInCurrency = displayInCurrency === 'true'; displayInCurrency = displayInCurrency === 'true';
if (displayInCurrency) { if (displayInCurrency) {
return '$' + calculateQuota(quota, digits); return '$' + calculateQuota(quota, digits);
} }
return renderNumber(quota); return renderNumber(quota);
} }
export const verifyJSON = (str) => { export const verifyJSON = (str) => {
try { try {
JSON.parse(str); JSON.parse(str);
} catch (e) { } catch (e) {
return false; return false;
} }
return true; return true;
}; };
export function renderNumber(num) { export function renderNumber(num) {
if (num >= 1000000000) { if (num >= 1000000000) {
return (num / 1000000000).toFixed(1) + 'B'; return (num / 1000000000).toFixed(1) + 'B';
} else if (num >= 1000000) { } else if (num >= 1000000) {
return (num / 1000000).toFixed(1) + 'M'; return (num / 1000000).toFixed(1) + 'M';
} else if (num >= 10000) { } else if (num >= 10000) {
return (num / 1000).toFixed(1) + 'k'; return (num / 1000).toFixed(1) + 'k';
} else { } else {
return num; return num;
} }
} }
export function renderQuotaWithPrompt(quota, digits) { export function renderQuotaWithPrompt(quota, digits) {
let displayInCurrency = localStorage.getItem('display_in_currency'); let displayInCurrency = localStorage.getItem('display_in_currency');
displayInCurrency = displayInCurrency === 'true'; displayInCurrency = displayInCurrency === 'true';
if (displayInCurrency) { if (displayInCurrency) {
return `(等价金额:${renderQuota(quota, digits)}`; return `(等价金额:${renderQuota(quota, digits)}`;
} }
return ''; return '';
} }
export function downloadTextAsFile(text, filename) { export function downloadTextAsFile(text, filename) {
let blob = new Blob([text], { type: 'text/plain;charset=utf-8' }); let blob = new Blob([text], {type: 'text/plain;charset=utf-8'});
let url = URL.createObjectURL(blob); let url = URL.createObjectURL(blob);
let a = document.createElement('a'); let a = document.createElement('a');
a.href = url; a.href = url;
a.download = filename; a.download = filename;
a.click(); a.click();
} }
export function removeTrailingSlash(url) { export function removeTrailingSlash(url) {
if (url.endsWith('/')) { if (url.endsWith('/')) {
return url.slice(0, -1); return url.slice(0, -1);
} else { } else {
return url; return url;
} }
} }
let channelModels = undefined; let channelModels = undefined;
export async function loadChannelModels() { export async function loadChannelModels() {
const res = await API.get('/api/models'); const res = await API.get('/api/models');
const { success, data } = res.data; const {success, data} = res.data;
if (!success) { if (!success) {
return; return;
} }
channelModels = data; channelModels = data;
localStorage.setItem('channel_models', JSON.stringify(data)); localStorage.setItem('channel_models', JSON.stringify(data));
} }
export function getChannelModels(type) { export function getChannelModels(type) {
if (channelModels !== undefined && type in channelModels) { if (channelModels !== undefined && type in channelModels) {
return channelModels[type]; return channelModels[type];
} }
let models = localStorage.getItem('channel_models'); let models = localStorage.getItem('channel_models');
if (!models) { if (!models) {
return [];
}
channelModels = JSON.parse(models);
if (type in channelModels) {
return channelModels[type];
}
return []; return [];
}
channelModels = JSON.parse(models);
if (type in channelModels) {
return channelModels[type];
}
return [];
} }
export function copy(text, name = '') { export function copy(text, name = '') {
try { if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text); navigator.clipboard.writeText(text).then(() => {
} catch (error) { showNotice(`复制${name}成功!`, true);
text = `复制${name}失败,请手动复制:<br /><br />${text}`; }, () => {
enqueueSnackbar(<SnackbarHTMLContent htmlContent={text} />, getSnackbarOptions('COPY')); text = `复制${name}失败,请手动复制:<br /><br />${text}`;
return; enqueueSnackbar(<SnackbarHTMLContent htmlContent={text}/>, getSnackbarOptions('COPY'));
} });
showSuccess(`复制${name}成功!`); } else {
const textArea = document.createElement("textarea");
textArea.value = text;
document.body.appendChild(textArea);
textArea.select();
try {
document.execCommand('copy');
showNotice(`复制${name}成功!`, true);
} catch (err) {
text = `复制${name}失败,请手动复制:<br /><br />${text}`;
enqueueSnackbar(<SnackbarHTMLContent htmlContent={text}/>, getSnackbarOptions('COPY'));
}
document.body.removeChild(textArea);
}
} }