mirror of
https://github.com/linux-do/new-api.git
synced 2025-11-17 19:13:42 +08:00
Compare commits
26 Commits
v0.2.1.0-a
...
v0.2.2.0-a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bdc65bdba2 | ||
|
|
76dc7af8d1 | ||
|
|
892b7d1ad4 | ||
|
|
6b71db7ce2 | ||
|
|
b8fb351fd8 | ||
|
|
e6765ef32d | ||
|
|
4ef98ba7eb | ||
|
|
65b85377c6 | ||
|
|
c6e85d5b57 | ||
|
|
1162683b4d | ||
|
|
818bd824da | ||
|
|
6e54f01435 | ||
|
|
505916b755 | ||
|
|
a4defe6ada | ||
|
|
9dfd405ba9 | ||
|
|
6c5b94ceb0 | ||
|
|
ac2984315a | ||
|
|
848358d876 | ||
|
|
e9abe5b705 | ||
|
|
d7e117acf5 | ||
|
|
1456992aae | ||
|
|
3b6ea51033 | ||
|
|
21250a46a6 | ||
|
|
b31fadd74f | ||
|
|
97af77b26c | ||
|
|
08e10df887 |
@@ -9,15 +9,6 @@ import (
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// Pay Settings
|
||||
|
||||
var PayAddress = ""
|
||||
var CustomCallbackAddress = ""
|
||||
var EpayId = ""
|
||||
var EpayKey = ""
|
||||
var Price = 7.3
|
||||
var MinTopUp = 1
|
||||
|
||||
var StartTime = time.Now().Unix() // unit: second
|
||||
var Version = "v0.0.0" // this hard coding will be replaced automatically when building, no need to manually change
|
||||
var SystemName = "New API"
|
||||
|
||||
@@ -12,14 +12,15 @@ import (
|
||||
// TODO: when a new api is enabled, check the pricing here
|
||||
// 1 === $0.002 / 1K tokens
|
||||
// 1 === ¥0.014 / 1k tokens
|
||||
|
||||
var DefaultModelRatio = map[string]float64{
|
||||
//"midjourney": 50,
|
||||
"gpt-4-gizmo-*": 15,
|
||||
"gpt-4": 15,
|
||||
"gpt-4-0314": 15,
|
||||
"gpt-4-0613": 15,
|
||||
"gpt-4-32k": 30,
|
||||
"gpt-4-32k-0314": 30,
|
||||
"gpt-4-gizmo-*": 15,
|
||||
"gpt-4": 15,
|
||||
//"gpt-4-0314": 15, //deprecated
|
||||
"gpt-4-0613": 15,
|
||||
"gpt-4-32k": 30,
|
||||
//"gpt-4-32k-0314": 30, //deprecated
|
||||
"gpt-4-32k-0613": 30,
|
||||
"gpt-4-1106-preview": 5, // $0.01 / 1K tokens
|
||||
"gpt-4-0125-preview": 5, // $0.01 / 1K tokens
|
||||
@@ -28,74 +29,77 @@ var DefaultModelRatio = map[string]float64{
|
||||
"gpt-4-1106-vision-preview": 5, // $0.01 / 1K tokens
|
||||
"gpt-4-turbo": 5, // $0.01 / 1K tokens
|
||||
"gpt-3.5-turbo": 0.25, // $0.0015 / 1K tokens
|
||||
"gpt-3.5-turbo-0301": 0.75,
|
||||
"gpt-3.5-turbo-0613": 0.75,
|
||||
"gpt-3.5-turbo-16k": 1.5, // $0.003 / 1K tokens
|
||||
"gpt-3.5-turbo-16k-0613": 1.5,
|
||||
"gpt-3.5-turbo-instruct": 0.75, // $0.0015 / 1K tokens
|
||||
"gpt-3.5-turbo-1106": 0.5, // $0.001 / 1K tokens
|
||||
"gpt-3.5-turbo-0125": 0.25,
|
||||
"babbage-002": 0.2, // $0.0004 / 1K tokens
|
||||
"davinci-002": 1, // $0.002 / 1K tokens
|
||||
"text-ada-001": 0.2,
|
||||
"text-babbage-001": 0.25,
|
||||
"text-curie-001": 1,
|
||||
"text-davinci-002": 10,
|
||||
"text-davinci-003": 10,
|
||||
"text-davinci-edit-001": 10,
|
||||
"code-davinci-edit-001": 10,
|
||||
"whisper-1": 15, // $0.006 / minute -> $0.006 / 150 words -> $0.006 / 200 tokens -> $0.03 / 1k tokens
|
||||
"tts-1": 7.5, // 1k characters -> $0.015
|
||||
"tts-1-1106": 7.5, // 1k characters -> $0.015
|
||||
"tts-1-hd": 15, // 1k characters -> $0.03
|
||||
"tts-1-hd-1106": 15, // 1k characters -> $0.03
|
||||
"davinci": 10,
|
||||
"curie": 10,
|
||||
"babbage": 10,
|
||||
"ada": 10,
|
||||
"text-embedding-3-small": 0.01,
|
||||
"text-embedding-3-large": 0.065,
|
||||
"text-embedding-ada-002": 0.05,
|
||||
"text-search-ada-doc-001": 10,
|
||||
"text-moderation-stable": 0.1,
|
||||
"text-moderation-latest": 0.1,
|
||||
"dall-e-2": 8,
|
||||
"dall-e-3": 16,
|
||||
"claude-instant-1": 0.4, // $0.8 / 1M tokens
|
||||
"claude-2.0": 4, // $8 / 1M tokens
|
||||
"claude-2.1": 4, // $8 / 1M tokens
|
||||
"claude-3-haiku-20240307": 0.125, // $0.25 / 1M tokens
|
||||
"claude-3-sonnet-20240229": 1.5, // $3 / 1M tokens
|
||||
"claude-3-opus-20240229": 7.5, // $15 / 1M tokens
|
||||
"ERNIE-Bot": 0.8572, // ¥0.012 / 1k tokens
|
||||
"ERNIE-Bot-turbo": 0.5715, // ¥0.008 / 1k tokens
|
||||
"ERNIE-Bot-4": 8.572, // ¥0.12 / 1k tokens
|
||||
"Embedding-V1": 0.1429, // ¥0.002 / 1k tokens
|
||||
"PaLM-2": 1,
|
||||
"gemini-pro": 1, // $0.00025 / 1k characters -> $0.001 / 1k tokens
|
||||
"gemini-pro-vision": 1, // $0.00025 / 1k characters -> $0.001 / 1k tokens
|
||||
"gemini-1.0-pro-vision-001": 1,
|
||||
"gemini-1.0-pro-001": 1,
|
||||
"gemini-1.5-pro": 1,
|
||||
"chatglm_turbo": 0.3572, // ¥0.005 / 1k tokens
|
||||
"chatglm_pro": 0.7143, // ¥0.01 / 1k tokens
|
||||
"chatglm_std": 0.3572, // ¥0.005 / 1k tokens
|
||||
"chatglm_lite": 0.1429, // ¥0.002 / 1k tokens
|
||||
"glm-4": 7.143, // ¥0.1 / 1k tokens
|
||||
"glm-4v": 7.143, // ¥0.1 / 1k tokens
|
||||
"glm-3-turbo": 0.3572,
|
||||
"qwen-turbo": 0.8572, // ¥0.012 / 1k tokens
|
||||
"qwen-plus": 10, // ¥0.14 / 1k tokens
|
||||
"text-embedding-v1": 0.05, // ¥0.0007 / 1k tokens
|
||||
"SparkDesk-v1.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.5": 1.2858, // ¥0.018 / 1k tokens
|
||||
"360GPT_S2_V9": 0.8572, // ¥0.012 / 1k tokens
|
||||
"embedding-bert-512-v1": 0.0715, // ¥0.001 / 1k tokens
|
||||
"embedding_s1_v1": 0.0715, // ¥0.001 / 1k tokens
|
||||
"semantic_similarity_s1_v1": 0.0715, // ¥0.001 / 1k tokens
|
||||
"hunyuan": 7.143, // ¥0.1 / 1k tokens // https://cloud.tencent.com/document/product/1729/97731#e0e6be58-60c8-469f-bdeb-6c264ce3b4d0
|
||||
//"gpt-3.5-turbo-0301": 0.75, //deprecated
|
||||
"gpt-3.5-turbo-0613": 0.75,
|
||||
"gpt-3.5-turbo-16k": 1.5, // $0.003 / 1K tokens
|
||||
"gpt-3.5-turbo-16k-0613": 1.5,
|
||||
"gpt-3.5-turbo-instruct": 0.75, // $0.0015 / 1K tokens
|
||||
"gpt-3.5-turbo-1106": 0.5, // $0.001 / 1K tokens
|
||||
"gpt-3.5-turbo-0125": 0.25,
|
||||
"babbage-002": 0.2, // $0.0004 / 1K tokens
|
||||
"davinci-002": 1, // $0.002 / 1K tokens
|
||||
"text-ada-001": 0.2,
|
||||
"text-babbage-001": 0.25,
|
||||
"text-curie-001": 1,
|
||||
"text-davinci-002": 10,
|
||||
"text-davinci-003": 10,
|
||||
"text-davinci-edit-001": 10,
|
||||
"code-davinci-edit-001": 10,
|
||||
"whisper-1": 15, // $0.006 / minute -> $0.006 / 150 words -> $0.006 / 200 tokens -> $0.03 / 1k tokens
|
||||
"tts-1": 7.5, // 1k characters -> $0.015
|
||||
"tts-1-1106": 7.5, // 1k characters -> $0.015
|
||||
"tts-1-hd": 15, // 1k characters -> $0.03
|
||||
"tts-1-hd-1106": 15, // 1k characters -> $0.03
|
||||
"davinci": 10,
|
||||
"curie": 10,
|
||||
"babbage": 10,
|
||||
"ada": 10,
|
||||
"text-embedding-3-small": 0.01,
|
||||
"text-embedding-3-large": 0.065,
|
||||
"text-embedding-ada-002": 0.05,
|
||||
"text-search-ada-doc-001": 10,
|
||||
"text-moderation-stable": 0.1,
|
||||
"text-moderation-latest": 0.1,
|
||||
"dall-e-2": 8,
|
||||
"dall-e-3": 16,
|
||||
"claude-instant-1": 0.4, // $0.8 / 1M tokens
|
||||
"claude-2.0": 4, // $8 / 1M tokens
|
||||
"claude-2.1": 4, // $8 / 1M tokens
|
||||
"claude-3-haiku-20240307": 0.125, // $0.25 / 1M tokens
|
||||
"claude-3-sonnet-20240229": 1.5, // $3 / 1M tokens
|
||||
"claude-3-opus-20240229": 7.5, // $15 / 1M tokens
|
||||
"ERNIE-Bot": 0.8572, // ¥0.012 / 1k tokens
|
||||
"ERNIE-Bot-turbo": 0.5715, // ¥0.008 / 1k tokens
|
||||
"ERNIE-Bot-4": 8.572, // ¥0.12 / 1k tokens
|
||||
"Embedding-V1": 0.1429, // ¥0.002 / 1k tokens
|
||||
"PaLM-2": 1,
|
||||
"gemini-pro": 1, // $0.00025 / 1k characters -> $0.001 / 1k tokens
|
||||
"gemini-pro-vision": 1, // $0.00025 / 1k characters -> $0.001 / 1k tokens
|
||||
"gemini-1.0-pro-vision-001": 1,
|
||||
"gemini-1.0-pro-001": 1,
|
||||
"gemini-1.5-pro-latest": 1,
|
||||
"gemini-1.0-pro-latest": 1,
|
||||
"gemini-1.0-pro-vision-latest": 1,
|
||||
"gemini-ultra": 1,
|
||||
"chatglm_turbo": 0.3572, // ¥0.005 / 1k tokens
|
||||
"chatglm_pro": 0.7143, // ¥0.01 / 1k tokens
|
||||
"chatglm_std": 0.3572, // ¥0.005 / 1k tokens
|
||||
"chatglm_lite": 0.1429, // ¥0.002 / 1k tokens
|
||||
"glm-4": 7.143, // ¥0.1 / 1k tokens
|
||||
"glm-4v": 7.143, // ¥0.1 / 1k tokens
|
||||
"glm-3-turbo": 0.3572,
|
||||
"qwen-turbo": 0.8572, // ¥0.012 / 1k tokens
|
||||
"qwen-plus": 10, // ¥0.14 / 1k tokens
|
||||
"text-embedding-v1": 0.05, // ¥0.0007 / 1k tokens
|
||||
"SparkDesk-v1.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.5": 1.2858, // ¥0.018 / 1k tokens
|
||||
"360GPT_S2_V9": 0.8572, // ¥0.012 / 1k tokens
|
||||
"embedding-bert-512-v1": 0.0715, // ¥0.001 / 1k tokens
|
||||
"embedding_s1_v1": 0.0715, // ¥0.001 / 1k tokens
|
||||
"semantic_similarity_s1_v1": 0.0715, // ¥0.001 / 1k tokens
|
||||
"hunyuan": 7.143, // ¥0.1 / 1k tokens // https://cloud.tencent.com/document/product/1729/97731#e0e6be58-60c8-469f-bdeb-6c264ce3b4d0
|
||||
// https://platform.lingyiwanwu.com/docs#-计费单元
|
||||
// 已经按照 7.2 来换算美元价格
|
||||
"yi-34b-chat-0205": 0.018,
|
||||
@@ -202,7 +206,7 @@ func GetCompletionRatio(name string) float64 {
|
||||
return 4.0 / 3.0
|
||||
}
|
||||
if strings.HasPrefix(name, "gpt-4") {
|
||||
if strings.HasPrefix(name, "gpt-4-turbo")|| strings.HasSuffix(name, "preview") {
|
||||
if strings.HasPrefix(name, "gpt-4-turbo") || strings.HasSuffix(name, "preview") {
|
||||
return 3
|
||||
}
|
||||
return 2
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package constant
|
||||
|
||||
var MjNotifyEnabled = false
|
||||
|
||||
var MjModeClearEnabled = false
|
||||
var MjForwardUrlEnabled = true
|
||||
|
||||
const (
|
||||
MjErrorUnknown = 5
|
||||
|
||||
8
constant/payment.go
Normal file
8
constant/payment.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package constant
|
||||
|
||||
var PayAddress = ""
|
||||
var CustomCallbackAddress = ""
|
||||
var EpayId = ""
|
||||
var EpayKey = ""
|
||||
var Price = 7.3
|
||||
var MinTopUp = 1
|
||||
@@ -10,11 +10,11 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
"one-api/common"
|
||||
"one-api/constant"
|
||||
"one-api/dto"
|
||||
"one-api/model"
|
||||
"one-api/service"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -233,6 +233,12 @@ func GetAllMidjourney(c *gin.Context) {
|
||||
if logs == nil {
|
||||
logs = make([]*model.Midjourney, 0)
|
||||
}
|
||||
if constant.MjForwardUrlEnabled {
|
||||
for i, midjourney := range logs {
|
||||
midjourney.ImageUrl = common.ServerAddress + "/mj/image/" + midjourney.MjId
|
||||
logs[i] = midjourney
|
||||
}
|
||||
}
|
||||
c.JSON(200, gin.H{
|
||||
"success": true,
|
||||
"message": "",
|
||||
@@ -259,7 +265,7 @@ func GetUserMidjourney(c *gin.Context) {
|
||||
if logs == nil {
|
||||
logs = make([]*model.Midjourney, 0)
|
||||
}
|
||||
if !strings.Contains(common.ServerAddress, "localhost") {
|
||||
if constant.MjForwardUrlEnabled {
|
||||
for i, midjourney := range logs {
|
||||
midjourney.ImageUrl = common.ServerAddress + "/mj/image/" + midjourney.MjId
|
||||
logs[i] = midjourney
|
||||
|
||||
@@ -46,8 +46,8 @@ func GetStatus(c *gin.Context) {
|
||||
"wechat_qrcode": common.WeChatAccountQRCodeImageURL,
|
||||
"wechat_login": common.WeChatAuthEnabled,
|
||||
"server_address": common.ServerAddress,
|
||||
"price": common.Price,
|
||||
"min_topup": common.MinTopUp,
|
||||
"price": constant.Price,
|
||||
"min_topup": constant.MinTopUp,
|
||||
"turnstile_check": common.TurnstileCheckEnabled,
|
||||
"turnstile_site_key": common.TurnstileSiteKey,
|
||||
"top_up_link": common.TopUpLink,
|
||||
@@ -60,7 +60,7 @@ func GetStatus(c *gin.Context) {
|
||||
"enable_data_export": common.DataExportEnabled,
|
||||
"data_export_default_time": common.DataExportDefaultTime,
|
||||
"default_collapse_sidebar": common.DefaultCollapseSidebar,
|
||||
"enable_online_topup": common.PayAddress != "" && common.EpayId != "" && common.EpayKey != "",
|
||||
"enable_online_topup": constant.PayAddress != "" && constant.EpayId != "" && constant.EpayKey != "",
|
||||
"mj_notify_enabled": constant.MjNotifyEnabled,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -14,7 +14,7 @@ func GetOptions(c *gin.Context) {
|
||||
var options []*model.Option
|
||||
common.OptionMapRWMutex.Lock()
|
||||
for k, v := range common.OptionMap {
|
||||
if strings.HasSuffix(k, "Token") || strings.HasSuffix(k, "Secret") {
|
||||
if strings.HasSuffix(k, "Token") || strings.HasSuffix(k, "Secret") || strings.HasSuffix(k, "Key") {
|
||||
continue
|
||||
}
|
||||
options = append(options, &model.Option{
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"one-api/relay/constant"
|
||||
relayconstant "one-api/relay/constant"
|
||||
"one-api/service"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func relayHandler(c *gin.Context, relayMode int) *dto.OpenAIErrorWithStatusCode {
|
||||
@@ -42,7 +43,7 @@ func Relay(c *gin.Context) {
|
||||
group := c.GetString("group")
|
||||
originalModel := c.GetString("original_model")
|
||||
openaiErr := relayHandler(c, relayMode)
|
||||
retryLogStr := fmt.Sprintf("重试:%d", channelId)
|
||||
useChannel := []int{channelId}
|
||||
if openaiErr != nil {
|
||||
go processChannelError(c, channelId, openaiErr)
|
||||
} else {
|
||||
@@ -55,7 +56,7 @@ func Relay(c *gin.Context) {
|
||||
break
|
||||
}
|
||||
channelId = channel.Id
|
||||
retryLogStr += fmt.Sprintf("->%d", channel.Id)
|
||||
useChannel = append(useChannel, channelId)
|
||||
common.LogInfo(c.Request.Context(), fmt.Sprintf("using channel #%d to retry (remain times %d)", channel.Id, i))
|
||||
middleware.SetupContextForSelectedChannel(c, channel, originalModel)
|
||||
|
||||
@@ -66,7 +67,10 @@ func Relay(c *gin.Context) {
|
||||
go processChannelError(c, channelId, openaiErr)
|
||||
}
|
||||
}
|
||||
common.LogInfo(c.Request.Context(), retryLogStr)
|
||||
if len(useChannel) > 1 {
|
||||
retryLogStr := fmt.Sprintf("重试:%s", strings.Trim(strings.Join(strings.Fields(fmt.Sprint(useChannel)), "->"), "[]"))
|
||||
common.LogInfo(c.Request.Context(), retryLogStr)
|
||||
}
|
||||
|
||||
if openaiErr != nil {
|
||||
if openaiErr.StatusCode == http.StatusTooManyRequests {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"github.com/Calcium-Ion/go-epay/epay"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/samber/lo"
|
||||
"one-api/constant"
|
||||
|
||||
"log"
|
||||
"net/url"
|
||||
@@ -28,13 +29,13 @@ type AmountRequest struct {
|
||||
}
|
||||
|
||||
func GetEpayClient() *epay.Client {
|
||||
if common.PayAddress == "" || common.EpayId == "" || common.EpayKey == "" {
|
||||
if constant.PayAddress == "" || constant.EpayId == "" || constant.EpayKey == "" {
|
||||
return nil
|
||||
}
|
||||
withUrl, err := epay.NewClient(&epay.Config{
|
||||
PartnerID: common.EpayId,
|
||||
Key: common.EpayKey,
|
||||
}, common.PayAddress)
|
||||
PartnerID: constant.EpayId,
|
||||
Key: constant.EpayKey,
|
||||
}, constant.PayAddress)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -50,12 +51,12 @@ func getPayMoney(amount float64, user model.User) float64 {
|
||||
if topupGroupRatio == 0 {
|
||||
topupGroupRatio = 1
|
||||
}
|
||||
payMoney := amount * common.Price * topupGroupRatio
|
||||
payMoney := amount * constant.Price * topupGroupRatio
|
||||
return payMoney
|
||||
}
|
||||
|
||||
func getMinTopup() int {
|
||||
minTopup := common.MinTopUp
|
||||
minTopup := constant.MinTopUp
|
||||
if !common.DisplayInCurrencyEnabled {
|
||||
minTopup = minTopup * int(common.QuotaPerUnit)
|
||||
}
|
||||
|
||||
@@ -54,13 +54,29 @@ type OpenAIEmbeddingResponse struct {
|
||||
}
|
||||
|
||||
type ChatCompletionsStreamResponseChoice struct {
|
||||
Delta struct {
|
||||
Content string `json:"content"`
|
||||
Role string `json:"role,omitempty"`
|
||||
ToolCalls any `json:"tool_calls,omitempty"`
|
||||
} `json:"delta"`
|
||||
FinishReason *string `json:"finish_reason,omitempty"`
|
||||
Index int `json:"index,omitempty"`
|
||||
Delta ChatCompletionsStreamResponseChoiceDelta `json:"delta"`
|
||||
FinishReason *string `json:"finish_reason,omitempty"`
|
||||
Index int `json:"index,omitempty"`
|
||||
}
|
||||
|
||||
type ChatCompletionsStreamResponseChoiceDelta struct {
|
||||
Content string `json:"content"`
|
||||
Role string `json:"role,omitempty"`
|
||||
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
|
||||
}
|
||||
|
||||
type ToolCall struct {
|
||||
// Index is not nil only in chat completion chunk object
|
||||
Index *int `json:"index,omitempty"`
|
||||
ID string `json:"id"`
|
||||
Type any `json:"type"`
|
||||
Function FunctionCall `json:"function"`
|
||||
}
|
||||
|
||||
type FunctionCall struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
// call function with arguments in JSON format
|
||||
Arguments string `json:"arguments,omitempty"`
|
||||
}
|
||||
|
||||
type ChatCompletionsStreamResponse struct {
|
||||
|
||||
2
makefile
2
makefile
@@ -7,7 +7,7 @@ all: build-frontend start-backend
|
||||
|
||||
build-frontend:
|
||||
@echo "Building frontend..."
|
||||
@cd $(FRONTEND_DIR) && npm install && DISABLE_ESLINT_PLUGIN='true' REACT_APP_VERSION=$(cat VERSION) npm run build npm run build
|
||||
@cd $(FRONTEND_DIR) && npm install && DISABLE_ESLINT_PLUGIN='true' VITE_REACT_APP_VERSION=$(cat VERSION) npm run build
|
||||
|
||||
start-backend:
|
||||
@echo "Starting backend dev server..."
|
||||
|
||||
@@ -177,6 +177,7 @@ func SetupContextForSelectedChannel(c *gin.Context, channel *model.Channel, mode
|
||||
}
|
||||
c.Set("auto_ban", ban)
|
||||
c.Set("model_mapping", channel.GetModelMapping())
|
||||
c.Set("status_code_mapping", channel.GetStatusCodeMapping())
|
||||
c.Request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", channel.Key))
|
||||
c.Set("base_url", channel.GetBaseURL())
|
||||
// TODO: api_version统一
|
||||
|
||||
@@ -25,9 +25,6 @@ var token2UserId = make(map[string]int)
|
||||
var token2UserIdLock sync.RWMutex
|
||||
|
||||
func cacheSetToken(token *Token) error {
|
||||
if !common.RedisEnabled {
|
||||
return token.SelectUpdate()
|
||||
}
|
||||
jsonBytes, err := json.Marshal(token)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -168,7 +165,11 @@ func CacheUpdateUserQuota(id int) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = common.RedisSet(fmt.Sprintf("user_quota:%d", id), fmt.Sprintf("%d", quota), time.Duration(UserId2QuotaCacheSeconds)*time.Second)
|
||||
return cacheSetUserQuota(id, quota)
|
||||
}
|
||||
|
||||
func cacheSetUserQuota(id int, quota int) error {
|
||||
err := common.RedisSet(fmt.Sprintf("user_quota:%d", id), fmt.Sprintf("%d", quota), time.Duration(UserId2QuotaCacheSeconds)*time.Second)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ type Channel struct {
|
||||
Group string `json:"group" gorm:"type:varchar(64);default:'default'"`
|
||||
UsedQuota int64 `json:"used_quota" gorm:"bigint;default:0"`
|
||||
ModelMapping *string `json:"model_mapping" gorm:"type:varchar(1024);default:''"`
|
||||
StatusCodeMapping *string `json:"status_code_mapping" gorm:"type:varchar(1024);default:''"`
|
||||
Priority *int64 `json:"priority" gorm:"bigint;default:0"`
|
||||
AutoBan *int `json:"auto_ban" gorm:"default:1"`
|
||||
}
|
||||
@@ -153,6 +154,13 @@ func (channel *Channel) GetModelMapping() string {
|
||||
return *channel.ModelMapping
|
||||
}
|
||||
|
||||
func (channel *Channel) GetStatusCodeMapping() string {
|
||||
if channel.StatusCodeMapping == nil {
|
||||
return ""
|
||||
}
|
||||
return *channel.StatusCodeMapping
|
||||
}
|
||||
|
||||
func (channel *Channel) Insert() error {
|
||||
var err error
|
||||
err = DB.Create(channel).Error
|
||||
|
||||
@@ -63,8 +63,8 @@ func InitOptionMap() {
|
||||
common.OptionMap["CustomCallbackAddress"] = ""
|
||||
common.OptionMap["EpayId"] = ""
|
||||
common.OptionMap["EpayKey"] = ""
|
||||
common.OptionMap["Price"] = strconv.FormatFloat(common.Price, 'f', -1, 64)
|
||||
common.OptionMap["MinTopUp"] = strconv.Itoa(common.MinTopUp)
|
||||
common.OptionMap["Price"] = strconv.FormatFloat(constant.Price, 'f', -1, 64)
|
||||
common.OptionMap["MinTopUp"] = strconv.Itoa(constant.MinTopUp)
|
||||
common.OptionMap["TopupGroupRatio"] = common.TopupGroupRatio2JSONString()
|
||||
common.OptionMap["GitHubClientId"] = ""
|
||||
common.OptionMap["GitHubClientSecret"] = ""
|
||||
@@ -93,6 +93,7 @@ func InitOptionMap() {
|
||||
common.OptionMap["DefaultCollapseSidebar"] = strconv.FormatBool(common.DefaultCollapseSidebar)
|
||||
common.OptionMap["MjNotifyEnabled"] = strconv.FormatBool(constant.MjNotifyEnabled)
|
||||
common.OptionMap["MjModeClearEnabled"] = strconv.FormatBool(constant.MjModeClearEnabled)
|
||||
common.OptionMap["MjForwardUrlEnabled"] = strconv.FormatBool(constant.MjForwardUrlEnabled)
|
||||
common.OptionMap["CheckSensitiveEnabled"] = strconv.FormatBool(constant.CheckSensitiveEnabled)
|
||||
common.OptionMap["CheckSensitiveOnPromptEnabled"] = strconv.FormatBool(constant.CheckSensitiveOnPromptEnabled)
|
||||
//common.OptionMap["CheckSensitiveOnCompletionEnabled"] = strconv.FormatBool(constant.CheckSensitiveOnCompletionEnabled)
|
||||
@@ -198,6 +199,8 @@ func updateOptionMap(key string, value string) (err error) {
|
||||
constant.MjNotifyEnabled = boolValue
|
||||
case "MjModeClearEnabled":
|
||||
constant.MjModeClearEnabled = boolValue
|
||||
case "MjForwardUrlEnabled":
|
||||
constant.MjForwardUrlEnabled = boolValue
|
||||
case "CheckSensitiveEnabled":
|
||||
constant.CheckSensitiveEnabled = boolValue
|
||||
case "CheckSensitiveOnPromptEnabled":
|
||||
@@ -227,17 +230,17 @@ func updateOptionMap(key string, value string) (err error) {
|
||||
case "ServerAddress":
|
||||
common.ServerAddress = value
|
||||
case "PayAddress":
|
||||
common.PayAddress = value
|
||||
constant.PayAddress = value
|
||||
case "CustomCallbackAddress":
|
||||
common.CustomCallbackAddress = value
|
||||
constant.CustomCallbackAddress = value
|
||||
case "EpayId":
|
||||
common.EpayId = value
|
||||
constant.EpayId = value
|
||||
case "EpayKey":
|
||||
common.EpayKey = value
|
||||
constant.EpayKey = value
|
||||
case "Price":
|
||||
common.Price, _ = strconv.ParseFloat(value, 64)
|
||||
constant.Price, _ = strconv.ParseFloat(value, 64)
|
||||
case "MinTopUp":
|
||||
common.MinTopUp, _ = strconv.Atoi(value)
|
||||
constant.MinTopUp, _ = strconv.Atoi(value)
|
||||
case "TopupGroupRatio":
|
||||
err = common.UpdateTopupGroupRatioByJSONString(value)
|
||||
case "GitHubClientId":
|
||||
|
||||
@@ -102,6 +102,11 @@ func GetTokenById(id int) (*Token, error) {
|
||||
token := Token{Id: id}
|
||||
var err error = nil
|
||||
err = DB.First(&token, "id = ?", id).Error
|
||||
if err != nil {
|
||||
if common.RedisEnabled {
|
||||
go cacheSetToken(&token)
|
||||
}
|
||||
}
|
||||
return &token, err
|
||||
}
|
||||
|
||||
|
||||
@@ -410,6 +410,11 @@ func ValidateAccessToken(token string) (user *User) {
|
||||
|
||||
func GetUserQuota(id int) (quota int, err error) {
|
||||
err = DB.Model(&User{}).Where("id = ?", id).Select("quota").Find("a).Error
|
||||
if err != nil {
|
||||
if common.RedisEnabled {
|
||||
go cacheSetUserQuota(id, quota)
|
||||
}
|
||||
}
|
||||
return quota, err
|
||||
}
|
||||
|
||||
|
||||
@@ -18,16 +18,28 @@ type Adaptor struct {
|
||||
func (a *Adaptor) Init(info *relaycommon.RelayInfo, request dto.GeneralOpenAIRequest) {
|
||||
}
|
||||
|
||||
// 定义一个映射,存储模型名称和对应的版本
|
||||
var modelVersionMap = map[string]string{
|
||||
"gemini-1.5-pro-latest": "v1beta",
|
||||
"gemini-ultra": "v1beta",
|
||||
}
|
||||
|
||||
func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
|
||||
version := "v1"
|
||||
if info.ApiVersion != "" {
|
||||
version = info.ApiVersion
|
||||
}
|
||||
action := "generateContent"
|
||||
if info.IsStream {
|
||||
action = "streamGenerateContent"
|
||||
}
|
||||
return fmt.Sprintf("%s/%s/models/%s:%s", info.BaseUrl, version, info.UpstreamModelName, action), nil
|
||||
// 从映射中获取模型名称对应的版本,如果找不到就使用 info.ApiVersion 或默认的版本 "v1"
|
||||
version, beta := modelVersionMap[info.UpstreamModelName]
|
||||
if !beta {
|
||||
if info.ApiVersion != "" {
|
||||
version = info.ApiVersion
|
||||
} else {
|
||||
version = "v1"
|
||||
}
|
||||
}
|
||||
|
||||
action := "generateContent"
|
||||
if info.IsStream {
|
||||
action = "streamGenerateContent"
|
||||
}
|
||||
return fmt.Sprintf("%s/%s/models/%s:%s", info.BaseUrl, version, info.UpstreamModelName, action), nil
|
||||
}
|
||||
|
||||
func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request, info *relaycommon.RelayInfo) error {
|
||||
|
||||
@@ -5,8 +5,8 @@ const (
|
||||
)
|
||||
|
||||
var ModelList = []string{
|
||||
"gemini-pro", "gemini-1.0-pro-001", "gemini-1.5-pro",
|
||||
"gemini-pro-vision", "gemini-1.0-pro-vision-001",
|
||||
"gemini-1.0-pro-latest", "gemini-1.0-pro-001", "gemini-1.5-pro-latest", "gemini-ultra",
|
||||
"gemini-1.0-pro-vision-latest", "gemini-1.0-pro-vision-001",
|
||||
}
|
||||
|
||||
var ChannelName = "google gemini"
|
||||
|
||||
@@ -68,6 +68,12 @@ func OpenaiStreamHandler(c *gin.Context, resp *http.Response, relayMode int) (*d
|
||||
if err == nil {
|
||||
for _, choice := range streamResponse.Choices {
|
||||
responseTextBuilder.WriteString(choice.Delta.Content)
|
||||
if choice.Delta.ToolCalls != nil {
|
||||
for _, tool := range choice.Delta.ToolCalls {
|
||||
responseTextBuilder.WriteString(tool.Function.Name)
|
||||
responseTextBuilder.WriteString(tool.Function.Arguments)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,6 +81,12 @@ func OpenaiStreamHandler(c *gin.Context, resp *http.Response, relayMode int) (*d
|
||||
for _, streamResponse := range streamResponses {
|
||||
for _, choice := range streamResponse.Choices {
|
||||
responseTextBuilder.WriteString(choice.Delta.Content)
|
||||
if choice.Delta.ToolCalls != nil {
|
||||
for _, tool := range choice.Delta.ToolCalls {
|
||||
responseTextBuilder.WriteString(tool.Function.Name)
|
||||
responseTextBuilder.WriteString(tool.Function.Arguments)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +179,13 @@ func xunfeiHandler(c *gin.Context, textRequest dto.GeneralOpenAIRequest, appId s
|
||||
case stop = <-stopChan:
|
||||
}
|
||||
}
|
||||
|
||||
if len(xunfeiResponse.Payload.Choices.Text) == 0 {
|
||||
xunfeiResponse.Payload.Choices.Text = []XunfeiChatResponseTextItem{
|
||||
{
|
||||
Content: "",
|
||||
},
|
||||
}
|
||||
}
|
||||
xunfeiResponse.Payload.Choices.Text[0].Content = content
|
||||
|
||||
response := responseXunfei2OpenAI(&xunfeiResponse)
|
||||
|
||||
@@ -110,11 +110,13 @@ func coverMidjourneyTaskDto(c *gin.Context, originTask *model.Midjourney) (midjo
|
||||
midjourneyTask.StartTime = originTask.StartTime
|
||||
midjourneyTask.FinishTime = originTask.FinishTime
|
||||
midjourneyTask.ImageUrl = ""
|
||||
if originTask.ImageUrl != "" {
|
||||
if originTask.ImageUrl != "" && constant.MjForwardUrlEnabled {
|
||||
midjourneyTask.ImageUrl = common.ServerAddress + "/mj/image/" + originTask.MjId
|
||||
if originTask.Status != "SUCCESS" {
|
||||
midjourneyTask.ImageUrl += "?rand=" + strconv.FormatInt(time.Now().UnixNano(), 10)
|
||||
}
|
||||
} else {
|
||||
midjourneyTask.ImageUrl = originTask.ImageUrl
|
||||
}
|
||||
midjourneyTask.Status = originTask.Status
|
||||
midjourneyTask.FailReason = originTask.FailReason
|
||||
|
||||
@@ -154,6 +154,7 @@ func TextHelper(c *gin.Context) *dto.OpenAIErrorWithStatusCode {
|
||||
requestBody = bytes.NewBuffer(jsonData)
|
||||
}
|
||||
|
||||
statusCodeMappingStr := c.GetString("status_code_mapping")
|
||||
resp, err := adaptor.DoRequest(c, relayInfo, requestBody)
|
||||
if err != nil {
|
||||
return service.OpenAIErrorWrapper(err, "do_request_failed", http.StatusInternalServerError)
|
||||
@@ -162,12 +163,17 @@ func TextHelper(c *gin.Context) *dto.OpenAIErrorWithStatusCode {
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
returnPreConsumedQuota(c, relayInfo.TokenId, userQuota, preConsumedQuota)
|
||||
return service.RelayErrorHandler(resp)
|
||||
openaiErr := service.RelayErrorHandler(resp)
|
||||
// reset status code 重置状态码
|
||||
service.ResetStatusCode(openaiErr, statusCodeMappingStr)
|
||||
return openaiErr
|
||||
}
|
||||
|
||||
usage, openaiErr := adaptor.DoResponse(c, resp, relayInfo)
|
||||
if openaiErr != nil {
|
||||
returnPreConsumedQuota(c, relayInfo.TokenId, userQuota, preConsumedQuota)
|
||||
// reset status code 重置状态码
|
||||
service.ResetStatusCode(openaiErr, statusCodeMappingStr)
|
||||
return openaiErr
|
||||
}
|
||||
postConsumeQuota(c, relayInfo, *textRequest, usage, ratio, preConsumedQuota, userQuota, modelRatio, groupRatio, modelPrice)
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package service
|
||||
|
||||
import "one-api/common"
|
||||
import (
|
||||
"one-api/common"
|
||||
"one-api/constant"
|
||||
)
|
||||
|
||||
func GetCallbackAddress() string {
|
||||
if common.CustomCallbackAddress == "" {
|
||||
if constant.CustomCallbackAddress == "" {
|
||||
return common.ServerAddress
|
||||
}
|
||||
return common.CustomCallbackAddress
|
||||
return constant.CustomCallbackAddress
|
||||
}
|
||||
|
||||
@@ -86,3 +86,22 @@ func RelayErrorHandler(resp *http.Response) (errWithStatusCode *dto.OpenAIErrorW
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ResetStatusCode(openaiErr *dto.OpenAIErrorWithStatusCode, statusCodeMappingStr string) {
|
||||
if statusCodeMappingStr == "" || statusCodeMappingStr == "{}" {
|
||||
return
|
||||
}
|
||||
statusCodeMapping := make(map[string]string)
|
||||
err := json.Unmarshal([]byte(statusCodeMappingStr), &statusCodeMapping)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if openaiErr.StatusCode == http.StatusOK {
|
||||
return
|
||||
}
|
||||
codeStr := strconv.Itoa(openaiErr.StatusCode)
|
||||
if _, ok := statusCodeMapping[codeStr]; ok {
|
||||
intCode, _ := strconv.Atoi(statusCodeMapping[codeStr])
|
||||
openaiErr.StatusCode = intCode
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,6 +211,23 @@ func CountTokenInput(input any, model string, check bool) (int, error, bool) {
|
||||
return CountTokenInput(fmt.Sprintf("%v", input), model, check)
|
||||
}
|
||||
|
||||
func CountTokenStreamChoices(messages []dto.ChatCompletionsStreamResponseChoice, model string) int {
|
||||
tokens := 0
|
||||
for _, message := range messages {
|
||||
tkm, _, _ := CountTokenInput(message.Delta.Content, model, false)
|
||||
tokens += tkm
|
||||
if message.Delta.ToolCalls != nil {
|
||||
for _, tool := range message.Delta.ToolCalls {
|
||||
tkm, _, _ := CountTokenInput(tool.Function.Name, model, false)
|
||||
tokens += tkm
|
||||
tkm, _, _ = CountTokenInput(tool.Function.Arguments, model, false)
|
||||
tokens += tkm
|
||||
}
|
||||
}
|
||||
}
|
||||
return tokens
|
||||
}
|
||||
|
||||
func CountAudioToken(text string, model string, check bool) (int, error, bool) {
|
||||
if strings.HasPrefix(model, "tts") {
|
||||
contains, words := SensitiveWordContains(text)
|
||||
|
||||
@@ -254,6 +254,19 @@ const ChannelsTable = () => {
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
<Popconfirm
|
||||
title='确定是否要复制此渠道?'
|
||||
content='复制渠道的所有信息'
|
||||
okType={'danger'}
|
||||
position={'left'}
|
||||
onConfirm={async () => {
|
||||
copySelectedChannel(record.id);
|
||||
}}
|
||||
>
|
||||
<Button theme='light' type='primary' style={{ marginRight: 1 }}>
|
||||
复制
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
@@ -340,6 +353,31 @@ const ChannelsTable = () => {
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const copySelectedChannel = async (id) => {
|
||||
const channelToCopy = channels.find(channel => String(channel.id) === String(id));
|
||||
console.log(channelToCopy)
|
||||
channelToCopy.name += '_复制';
|
||||
channelToCopy.created_time = null;
|
||||
channelToCopy.balance = 0;
|
||||
channelToCopy.used_quota = 0;
|
||||
if (!channelToCopy) {
|
||||
showError("渠道未找到,请刷新页面后重试。");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const newChannel = {...channelToCopy, id: undefined};
|
||||
const response = await API.post('/api/channel/', newChannel);
|
||||
if (response.data.success) {
|
||||
showSuccess("渠道复制成功");
|
||||
await refresh();
|
||||
} else {
|
||||
showError(response.data.message);
|
||||
}
|
||||
} catch (error) {
|
||||
showError("渠道复制失败: " + error.message);
|
||||
}
|
||||
};
|
||||
|
||||
const refresh = async () => {
|
||||
await loadChannels(activePage - 1, pageSize, idSort);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { UserContext } from '../context/User';
|
||||
import { useSetTheme, useTheme } from '../context/Theme';
|
||||
|
||||
import { API, getLogo, getSystemName, showSuccess } from '../helpers';
|
||||
import '../index.css';
|
||||
@@ -34,10 +35,8 @@ const HeaderBar = () => {
|
||||
let navigate = useNavigate();
|
||||
|
||||
const [showSidebar, setShowSidebar] = useState(false);
|
||||
const [dark, setDark] = useState(false);
|
||||
const systemName = getSystemName();
|
||||
const logo = getLogo();
|
||||
var themeMode = localStorage.getItem('theme-mode');
|
||||
const currentDate = new Date();
|
||||
// enable fireworks on new year(1.1 and 2.9-2.24)
|
||||
const isNewYear =
|
||||
@@ -66,26 +65,19 @@ const HeaderBar = () => {
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
const theme = useTheme();
|
||||
const setTheme = useSetTheme();
|
||||
|
||||
useEffect(() => {
|
||||
if (themeMode === 'dark') {
|
||||
switchMode(true);
|
||||
if (theme === 'dark') {
|
||||
document.body.setAttribute('theme-mode', 'dark');
|
||||
}
|
||||
|
||||
if (isNewYear) {
|
||||
console.log('Happy New Year!');
|
||||
}
|
||||
}, []);
|
||||
|
||||
const switchMode = (model) => {
|
||||
const body = document.body;
|
||||
if (!model) {
|
||||
body.removeAttribute('theme-mode');
|
||||
localStorage.setItem('theme-mode', 'light');
|
||||
} else {
|
||||
body.setAttribute('theme-mode', 'dark');
|
||||
localStorage.setItem('theme-mode', 'dark');
|
||||
}
|
||||
setDark(model);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Layout>
|
||||
@@ -132,9 +124,11 @@ const HeaderBar = () => {
|
||||
<Switch
|
||||
checkedText='🌞'
|
||||
size={'large'}
|
||||
checked={dark}
|
||||
checked={theme === 'dark'}
|
||||
uncheckedText='🌙'
|
||||
onChange={switchMode}
|
||||
onChange={(checked) => {
|
||||
setTheme(checked);
|
||||
}}
|
||||
/>
|
||||
{userState.user ? (
|
||||
<>
|
||||
|
||||
@@ -241,16 +241,25 @@ const LoginForm = () => {
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
||||
{status.telegram_oauth ? (
|
||||
<TelegramLoginButton
|
||||
dataOnauth={onTelegramLoginClicked}
|
||||
botName={status.telegram_bot_name}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
{status.telegram_oauth ? (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
marginTop: 5,
|
||||
}}
|
||||
>
|
||||
<TelegramLoginButton
|
||||
dataOnauth={onTelegramLoginClicked}
|
||||
botName={status.telegram_bot_name}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<></>
|
||||
|
||||
@@ -8,6 +8,8 @@ import {
|
||||
verifyJSON,
|
||||
} from '../helpers';
|
||||
|
||||
import { useTheme } from '../context/Theme';
|
||||
|
||||
const OperationSetting = () => {
|
||||
let now = new Date();
|
||||
let [inputs, setInputs] = useState({
|
||||
@@ -37,6 +39,7 @@ const OperationSetting = () => {
|
||||
SensitiveWords: '',
|
||||
MjNotifyEnabled: '',
|
||||
MjModeClearEnabled: '',
|
||||
MjForwardUrlEnabled: '',
|
||||
DrawingEnabled: '',
|
||||
DataExportEnabled: '',
|
||||
DataExportDefaultTime: 'hour',
|
||||
@@ -77,6 +80,9 @@ const OperationSetting = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const theme = useTheme();
|
||||
const isDark = theme === 'dark';
|
||||
|
||||
useEffect(() => {
|
||||
getOptions().then();
|
||||
}, []);
|
||||
@@ -219,8 +225,10 @@ const OperationSetting = () => {
|
||||
return (
|
||||
<Grid columns={1}>
|
||||
<Grid.Column>
|
||||
<Form loading={loading}>
|
||||
<Header as='h3'>通用设置</Header>
|
||||
<Form loading={loading} inverted={isDark}>
|
||||
<Header as='h3' inverted={isDark}>
|
||||
通用设置
|
||||
</Header>
|
||||
<Form.Group widths={4}>
|
||||
<Form.Input
|
||||
label='充值链接'
|
||||
@@ -299,7 +307,9 @@ const OperationSetting = () => {
|
||||
保存通用设置
|
||||
</Form.Button>
|
||||
<Divider />
|
||||
<Header as='h3'>绘图设置</Header>
|
||||
<Header as='h3' inverted={isDark}>
|
||||
绘图设置
|
||||
</Header>
|
||||
<Form.Group inline>
|
||||
<Form.Checkbox
|
||||
checked={inputs.DrawingEnabled === 'true'}
|
||||
@@ -313,6 +323,12 @@ const OperationSetting = () => {
|
||||
name='MjNotifyEnabled'
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
<Form.Checkbox
|
||||
checked={inputs.MjForwardUrlEnabled === 'true'}
|
||||
label='开启之后将上游地址替换为服务器地址'
|
||||
name='MjForwardUrlEnabled'
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
<Form.Checkbox
|
||||
checked={inputs.MjModeClearEnabled === 'true'}
|
||||
label='开启之后会清除用户提示词中的--fast、--relax以及--turbo参数'
|
||||
@@ -321,7 +337,9 @@ const OperationSetting = () => {
|
||||
/>
|
||||
</Form.Group>
|
||||
<Divider />
|
||||
<Header as='h3'>屏蔽词过滤设置</Header>
|
||||
<Header as='h3' inverted={isDark}>
|
||||
屏蔽词过滤设置
|
||||
</Header>
|
||||
<Form.Group inline>
|
||||
<Form.Checkbox
|
||||
checked={inputs.CheckSensitiveEnabled === 'true'}
|
||||
@@ -381,7 +399,9 @@ const OperationSetting = () => {
|
||||
保存屏蔽词设置
|
||||
</Form.Button>
|
||||
<Divider />
|
||||
<Header as='h3'>日志设置</Header>
|
||||
<Header as='h3' inverted={isDark}>
|
||||
日志设置
|
||||
</Header>
|
||||
<Form.Group inline>
|
||||
<Form.Checkbox
|
||||
checked={inputs.LogConsumeEnabled === 'true'}
|
||||
@@ -409,7 +429,9 @@ const OperationSetting = () => {
|
||||
清理历史日志
|
||||
</Form.Button>
|
||||
<Divider />
|
||||
<Header as='h3'>数据看板</Header>
|
||||
<Header as='h3' inverted={isDark}>
|
||||
数据看板
|
||||
</Header>
|
||||
<Form.Checkbox
|
||||
checked={inputs.DataExportEnabled === 'true'}
|
||||
label='启用数据看板(实验性)'
|
||||
@@ -439,7 +461,9 @@ const OperationSetting = () => {
|
||||
/>
|
||||
</Form.Group>
|
||||
<Divider />
|
||||
<Header as='h3'>监控设置</Header>
|
||||
<Header as='h3' inverted={isDark}>
|
||||
监控设置
|
||||
</Header>
|
||||
<Form.Group widths={3}>
|
||||
<Form.Input
|
||||
label='最长响应时间'
|
||||
@@ -484,7 +508,9 @@ const OperationSetting = () => {
|
||||
保存监控设置
|
||||
</Form.Button>
|
||||
<Divider />
|
||||
<Header as='h3'>额度设置</Header>
|
||||
<Header as='h3' inverted={isDark}>
|
||||
额度设置
|
||||
</Header>
|
||||
<Form.Group widths={4}>
|
||||
<Form.Input
|
||||
label='新用户初始额度'
|
||||
@@ -535,7 +561,9 @@ const OperationSetting = () => {
|
||||
保存额度设置
|
||||
</Form.Button>
|
||||
<Divider />
|
||||
<Header as='h3'>倍率设置</Header>
|
||||
<Header as='h3' inverted={isDark}>
|
||||
倍率设置
|
||||
</Header>
|
||||
<Form.Group widths='equal'>
|
||||
<Form.TextArea
|
||||
label='模型固定价格(一次调用消耗多少刀,优先级大于模型倍率)'
|
||||
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
} from 'semantic-ui-react';
|
||||
import { API, removeTrailingSlash, showError, verifyJSON } from '../helpers';
|
||||
|
||||
import { useTheme } from '../context/Theme';
|
||||
|
||||
const SystemSetting = () => {
|
||||
let [inputs, setInputs] = useState({
|
||||
PasswordLoginEnabled: '',
|
||||
@@ -57,6 +59,9 @@ const SystemSetting = () => {
|
||||
const [showPasswordWarningModal, setShowPasswordWarningModal] =
|
||||
useState(false);
|
||||
|
||||
const theme = useTheme();
|
||||
const isDark = theme === 'dark';
|
||||
|
||||
const getOptions = async () => {
|
||||
const res = await API.get('/api/option/');
|
||||
const { success, message, data } = res.data;
|
||||
@@ -306,8 +311,10 @@ const SystemSetting = () => {
|
||||
return (
|
||||
<Grid columns={1}>
|
||||
<Grid.Column>
|
||||
<Form loading={loading}>
|
||||
<Header as='h3'>通用设置</Header>
|
||||
<Form loading={loading} inverted={isDark}>
|
||||
<Header as='h3' inverted={isDark}>
|
||||
通用设置
|
||||
</Header>
|
||||
<Form.Group widths='equal'>
|
||||
<Form.Input
|
||||
label='服务器地址'
|
||||
@@ -321,7 +328,7 @@ const SystemSetting = () => {
|
||||
更新服务器地址
|
||||
</Form.Button>
|
||||
<Divider />
|
||||
<Header as='h3'>
|
||||
<Header as='h3' inverted={isDark}>
|
||||
支付设置(当前仅支持易支付接口,默认使用上方服务器地址作为回调地址!)
|
||||
</Header>
|
||||
<Form.Group widths='equal'>
|
||||
@@ -341,7 +348,7 @@ const SystemSetting = () => {
|
||||
/>
|
||||
<Form.Input
|
||||
label='易支付商户密钥'
|
||||
placeholder='例如:dejhfueqhujasjmndbjkqaw'
|
||||
placeholder='敏感信息不会发送到前端显示'
|
||||
value={inputs.EpayKey}
|
||||
name='EpayKey'
|
||||
onChange={handleInputChange}
|
||||
@@ -385,7 +392,9 @@ const SystemSetting = () => {
|
||||
</Form.Group>
|
||||
<Form.Button onClick={submitPayAddress}>更新支付设置</Form.Button>
|
||||
<Divider />
|
||||
<Header as='h3'>配置登录注册</Header>
|
||||
<Header as='h3' inverted={isDark}>
|
||||
配置登录注册
|
||||
</Header>
|
||||
<Form.Group inline>
|
||||
<Form.Checkbox
|
||||
checked={inputs.PasswordLoginEnabled === 'true'}
|
||||
@@ -468,7 +477,7 @@ const SystemSetting = () => {
|
||||
/>
|
||||
</Form.Group>
|
||||
<Divider />
|
||||
<Header as='h3'>
|
||||
<Header as='h3' inverted={isDark}>
|
||||
配置邮箱域名白名单
|
||||
<Header.Subheader>
|
||||
用以防止恶意用户利用临时邮箱批量注册
|
||||
@@ -533,7 +542,7 @@ const SystemSetting = () => {
|
||||
保存邮箱域名白名单设置
|
||||
</Form.Button>
|
||||
<Divider />
|
||||
<Header as='h3'>
|
||||
<Header as='h3' inverted={isDark}>
|
||||
配置 SMTP
|
||||
<Header.Subheader>用以支持系统的邮件发送</Header.Subheader>
|
||||
</Header>
|
||||
@@ -592,7 +601,7 @@ const SystemSetting = () => {
|
||||
</Form.Group>
|
||||
<Form.Button onClick={submitSMTP}>保存 SMTP 设置</Form.Button>
|
||||
<Divider />
|
||||
<Header as='h3'>
|
||||
<Header as='h3' inverted={isDark}>
|
||||
配置 GitHub OAuth App
|
||||
<Header.Subheader>
|
||||
用以支持通过 GitHub 进行登录注册,
|
||||
@@ -634,7 +643,7 @@ const SystemSetting = () => {
|
||||
保存 GitHub OAuth 设置
|
||||
</Form.Button>
|
||||
<Divider />
|
||||
<Header as='h3'>
|
||||
<Header as='h3' inverted={isDark}>
|
||||
配置 WeChat Server
|
||||
<Header.Subheader>
|
||||
用以支持通过微信进行登录注册,
|
||||
@@ -679,7 +688,9 @@ const SystemSetting = () => {
|
||||
保存 WeChat Server 设置
|
||||
</Form.Button>
|
||||
<Divider />
|
||||
<Header as='h3'>配置 Telegram 登录</Header>
|
||||
<Header as='h3' inverted={isDark}>
|
||||
配置 Telegram 登录
|
||||
</Header>
|
||||
<Form.Group inline>
|
||||
<Form.Input
|
||||
label='Telegram Bot Token'
|
||||
@@ -700,7 +711,7 @@ const SystemSetting = () => {
|
||||
保存 Telegram 登录设置
|
||||
</Form.Button>
|
||||
<Divider />
|
||||
<Header as='h3'>
|
||||
<Header as='h3' inverted={isDark}>
|
||||
配置 Turnstile
|
||||
<Header.Subheader>
|
||||
用以支持用户校验,
|
||||
|
||||
36
web/src/context/Theme/index.js
Normal file
36
web/src/context/Theme/index.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import { createContext, useCallback, useContext, useState } from 'react';
|
||||
|
||||
const ThemeContext = createContext(null);
|
||||
export const useTheme = () => useContext(ThemeContext);
|
||||
|
||||
const SetThemeContext = createContext(null);
|
||||
export const useSetTheme = () => useContext(SetThemeContext);
|
||||
|
||||
export const ThemeProvider = ({ children }) => {
|
||||
const [theme, _setTheme] = useState(() => {
|
||||
try {
|
||||
return localStorage.getItem('theme-mode') || null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
const setTheme = useCallback((input) => {
|
||||
_setTheme(input ? 'dark' : 'light');
|
||||
|
||||
const body = document.body;
|
||||
if (!input) {
|
||||
body.removeAttribute('theme-mode');
|
||||
localStorage.setItem('theme-mode', 'light');
|
||||
} else {
|
||||
body.setAttribute('theme-mode', 'dark');
|
||||
localStorage.setItem('theme-mode', 'dark');
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<SetThemeContext.Provider value={setTheme}>
|
||||
<ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider>
|
||||
</SetThemeContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -12,6 +12,7 @@ import 'react-toastify/dist/ReactToastify.css';
|
||||
import { StatusProvider } from './context/Status';
|
||||
import { Layout } from '@douyinfe/semi-ui';
|
||||
import SiderBar from './components/SiderBar';
|
||||
import { ThemeProvider } from './context/Theme';
|
||||
|
||||
// initialization
|
||||
|
||||
@@ -22,27 +23,29 @@ root.render(
|
||||
<StatusProvider>
|
||||
<UserProvider>
|
||||
<BrowserRouter>
|
||||
<Layout>
|
||||
<Sider>
|
||||
<SiderBar />
|
||||
</Sider>
|
||||
<ThemeProvider>
|
||||
<Layout>
|
||||
<Header>
|
||||
<HeaderBar />
|
||||
</Header>
|
||||
<Content
|
||||
style={{
|
||||
padding: '24px',
|
||||
}}
|
||||
>
|
||||
<App />
|
||||
</Content>
|
||||
<Layout.Footer>
|
||||
<Footer></Footer>
|
||||
</Layout.Footer>
|
||||
<Sider>
|
||||
<SiderBar />
|
||||
</Sider>
|
||||
<Layout>
|
||||
<Header>
|
||||
<HeaderBar />
|
||||
</Header>
|
||||
<Content
|
||||
style={{
|
||||
padding: '24px',
|
||||
}}
|
||||
>
|
||||
<App />
|
||||
</Content>
|
||||
<Layout.Footer>
|
||||
<Footer></Footer>
|
||||
</Layout.Footer>
|
||||
</Layout>
|
||||
<ToastContainer />
|
||||
</Layout>
|
||||
<ToastContainer />
|
||||
</Layout>
|
||||
</ThemeProvider>
|
||||
</BrowserRouter>
|
||||
</UserProvider>
|
||||
</StatusProvider>
|
||||
|
||||
@@ -29,6 +29,10 @@ const MODEL_MAPPING_EXAMPLE = {
|
||||
'gpt-4-32k-0314': 'gpt-4-32k',
|
||||
};
|
||||
|
||||
const STATUS_CODE_MAPPING_EXAMPLE = {
|
||||
400: '500',
|
||||
};
|
||||
|
||||
function type2secretPrompt(type) {
|
||||
// inputs.type === 15 ? '按照如下格式输入:APIKey|SecretKey' : (inputs.type === 18 ? '按照如下格式输入:APPID|APISecret|APIKey' : '请输入渠道对应的鉴权密钥')
|
||||
switch (type) {
|
||||
@@ -61,6 +65,7 @@ const EditChannel = (props) => {
|
||||
base_url: '',
|
||||
other: '',
|
||||
model_mapping: '',
|
||||
status_code_mapping: '',
|
||||
models: [],
|
||||
auto_ban: 1,
|
||||
test_model: '',
|
||||
@@ -136,7 +141,14 @@ const EditChannel = (props) => {
|
||||
localModels = ['hunyuan'];
|
||||
break;
|
||||
case 24:
|
||||
localModels = ['gemini-pro', 'gemini-pro-vision'];
|
||||
localModels = [
|
||||
'gemini-1.0-pro-001',
|
||||
'gemini-1.0-pro-vision-001',
|
||||
'gemini-1.5-pro',
|
||||
'gemini-1.5-pro-latest',
|
||||
'gemini-pro',
|
||||
'gemini-pro-vision',
|
||||
];
|
||||
break;
|
||||
case 25:
|
||||
localModels = [
|
||||
@@ -629,6 +641,36 @@ const EditChannel = (props) => {
|
||||
>
|
||||
填入模板
|
||||
</Typography.Text>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Typography.Text strong>
|
||||
状态码复写(仅影响本地判断,不修改返回到上游的状态码):
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<TextArea
|
||||
placeholder={`此项可选,用于复写返回的状态码,比如将claude渠道的400错误复写为500(用于重试),请勿滥用该功能,例如:\n${JSON.stringify(STATUS_CODE_MAPPING_EXAMPLE, null, 2)}`}
|
||||
name='status_code_mapping'
|
||||
onChange={(value) => {
|
||||
handleInputChange('status_code_mapping', value);
|
||||
}}
|
||||
autosize
|
||||
value={inputs.status_code_mapping}
|
||||
autoComplete='new-password'
|
||||
/>
|
||||
<Typography.Text
|
||||
style={{
|
||||
color: 'rgba(var(--semi-blue-5), 1)',
|
||||
userSelect: 'none',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
onClick={() => {
|
||||
handleInputChange(
|
||||
'status_code_mapping',
|
||||
JSON.stringify(STATUS_CODE_MAPPING_EXAMPLE, null, 2),
|
||||
);
|
||||
}}
|
||||
>
|
||||
填入模板
|
||||
</Typography.Text>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Typography.Text strong>密钥:</Typography.Text>
|
||||
</div>
|
||||
|
||||
@@ -72,6 +72,7 @@ const Detail = (props) => {
|
||||
stack: true,
|
||||
legends: {
|
||||
visible: true,
|
||||
selectMode: 'single',
|
||||
},
|
||||
title: {
|
||||
visible: true,
|
||||
@@ -216,6 +217,8 @@ const Detail = (props) => {
|
||||
} else if (dataExportDefaultTime === 'week') {
|
||||
timeGranularity = 604800;
|
||||
}
|
||||
// sort created_at
|
||||
data.sort((a, b) => a.created_at - b.created_at);
|
||||
data.forEach((item) => {
|
||||
item['created_at'] =
|
||||
Math.floor(item['created_at'] / timeGranularity) * timeGranularity;
|
||||
|
||||
Reference in New Issue
Block a user