diff --git a/common/constants.go b/common/constants.go index 99c78ac2..cb58391c 100644 --- a/common/constants.go +++ b/common/constants.go @@ -208,6 +208,8 @@ const ( ChannelTypeLingYiWanWu = 31 ChannelTypeAws = 33 ChannelTypeCohere = 34 + + ChannelTypeDummy // this one is only for count, do not add any channel after this ) var ChannelBaseURLs = []string{ diff --git a/common/model-ratio.go b/common/model-ratio.go index 9d6c4462..f4703173 100644 --- a/common/model-ratio.go +++ b/common/model-ratio.go @@ -28,6 +28,7 @@ var DefaultModelRatio = map[string]float64{ "gpt-4-vision-preview": 5, // $0.01 / 1K tokens "gpt-4-1106-vision-preview": 5, // $0.01 / 1K tokens "gpt-4-turbo": 5, // $0.01 / 1K tokens + "gpt-4-turbo-2024-04-09": 5, // $0.01 / 1K tokens "gpt-3.5-turbo": 0.25, // $0.0015 / 1K tokens //"gpt-3.5-turbo-0301": 0.75, //deprecated "gpt-3.5-turbo-0613": 0.75, @@ -111,6 +112,8 @@ var DefaultModelRatio = map[string]float64{ "command-light-nightly": 0.5, "command-r": 0.25, "command-r-plus ": 1.5, + "deepseek-chat": 0.07, + "deepseek-coder": 0.07, } var DefaultModelPrice = map[string]float64{ @@ -135,6 +138,12 @@ var DefaultModelPrice = map[string]float64{ var modelPrice map[string]float64 = nil var modelRatio map[string]float64 = nil +var CompletionRatio map[string]float64 = nil +var DefaultCompletionRatio = map[string]float64{ + "gpt-4-gizmo-*": 2, + "gpt-4-all": 2, +} + func ModelPrice2JSONString() string { if modelPrice == nil { modelPrice = DefaultModelPrice @@ -199,6 +208,22 @@ func GetModelRatio(name string) float64 { return ratio } +func CompletionRatio2JSONString() string { + if CompletionRatio == nil { + CompletionRatio = DefaultCompletionRatio + } + jsonBytes, err := json.Marshal(CompletionRatio) + if err != nil { + SysError("error marshalling completion ratio: " + err.Error()) + } + return string(jsonBytes) +} + +func UpdateCompletionRatioByJSONString(jsonStr string) error { + CompletionRatio = make(map[string]float64) + return json.Unmarshal([]byte(jsonStr), &CompletionRatio) +} + func GetCompletionRatio(name string) float64 { if strings.HasPrefix(name, "gpt-3.5") { if name == "gpt-3.5-turbo" || strings.HasSuffix(name, "0125") { @@ -211,7 +236,7 @@ func GetCompletionRatio(name string) float64 { } return 4.0 / 3.0 } - if strings.HasPrefix(name, "gpt-4") { + if strings.HasPrefix(name, "gpt-4") && name != "gpt-4-all" && !strings.HasPrefix(name, "gpt-4-gizmo") { if strings.HasPrefix(name, "gpt-4-turbo") || strings.HasSuffix(name, "preview") { return 3 } @@ -240,9 +265,19 @@ func GetCompletionRatio(name string) float64 { return 2 } } + if strings.HasPrefix(name, "deepseek") { + return 2 + } switch name { case "llama2-70b-4096": - return 0.8 / 0.7 + return 0.8 / 0.64 + case "llama3-8b-8192": + return 2 + case "llama3-70b-8192": + return 0.79 / 0.59 + } + if ratio, ok := CompletionRatio[name]; ok { + return ratio } return 1 } diff --git a/common/utils.go b/common/utils.go index d540c2e7..657ffd49 100644 --- a/common/utils.go +++ b/common/utils.go @@ -1,6 +1,7 @@ package common import ( + "encoding/json" "fmt" "github.com/google/uuid" "html/template" @@ -241,3 +242,11 @@ func RandomSleep() { // Sleep for 0-3000 ms time.Sleep(time.Duration(rand.Intn(3000)) * time.Millisecond) } + +func MapToJsonStr(m map[string]interface{}) string { + bytes, err := json.Marshal(m) + if err != nil { + return "" + } + return string(bytes) +} diff --git a/controller/channel-test.go b/controller/channel-test.go index f66e0d66..f37f309f 100644 --- a/controller/channel-test.go +++ b/controller/channel-test.go @@ -208,7 +208,7 @@ func testAllChannels(notify bool) error { if isChannelEnabled && service.ShouldDisableChannel(openaiErr, -1) && ban { service.DisableChannel(channel.Id, channel.Name, err.Error()) } - if !isChannelEnabled && service.ShouldEnableChannel(err, openaiErr) { + if !isChannelEnabled && service.ShouldEnableChannel(err, openaiErr, channel.Status) { service.EnableChannel(channel.Id, channel.Name) } channel.UpdateResponseTime(milliseconds) diff --git a/controller/misc.go b/controller/misc.go index 273a618e..8c59952b 100644 --- a/controller/misc.go +++ b/controller/misc.go @@ -147,7 +147,7 @@ func SendEmailVerification(c *gin.Context) { } } if common.EmailAliasRestrictionEnabled { - containsSpecialSymbols := strings.Contains(localPart, "+") || strings.Count(localPart, ".") > 1 + containsSpecialSymbols := strings.Contains(localPart, "+") || strings.Contains(localPart, ".") if containsSpecialSymbols { c.JSON(http.StatusOK, gin.H{ "success": false, diff --git a/controller/model.go b/controller/model.go index 1e78b4c5..890b2603 100644 --- a/controller/model.go +++ b/controller/model.go @@ -4,13 +4,15 @@ import ( "fmt" "github.com/gin-gonic/gin" "net/http" + "one-api/common" "one-api/constant" "one-api/dto" "one-api/model" "one-api/relay" "one-api/relay/channel/ai360" - "one-api/relay/channel/moonshot" "one-api/relay/channel/lingyiwanwu" + "one-api/relay/channel/moonshot" + relaycommon "one-api/relay/common" relayconstant "one-api/relay/constant" ) @@ -43,8 +45,9 @@ type OpenAIModels struct { var openAIModels []OpenAIModels var openAIModelsMap map[string]OpenAIModels +var channelId2Models map[int][]string -func init() { +func getPermission() []OpenAIModelPermission { var permission []OpenAIModelPermission permission = append(permission, OpenAIModelPermission{ Id: "modelperm-LwHkVFn8AcMItP432fKKDIKJ", @@ -60,7 +63,12 @@ func init() { Group: nil, IsBlocking: false, }) + return permission +} + +func init() { // https://platform.openai.com/docs/models/model-endpoint-compatibility + permission := getPermission() for i := 0; i < relayconstant.APITypeDummy; i++ { if i == relayconstant.APITypeAIProxyLibrary { continue @@ -85,7 +93,7 @@ func init() { Id: modelName, Object: "model", Created: 1626777600, - OwnedBy: "360", + OwnedBy: ai360.ChannelName, Permission: permission, Root: modelName, Parent: nil, @@ -128,6 +136,17 @@ func init() { for _, model := range openAIModels { openAIModelsMap[model.Id] = model } + channelId2Models = make(map[int][]string) + for i := 1; i <= common.ChannelTypeDummy; i++ { + apiType := relayconstant.ChannelType2APIType(i) + if apiType == -1 || apiType == relayconstant.APITypeAIProxyLibrary { + continue + } + meta := &relaycommon.RelayInfo{ChannelType: i} + adaptor := relay.GetAdaptor(apiType) + adaptor.Init(meta, dto.GeneralOpenAIRequest{}) + channelId2Models[i] = adaptor.GetModelList() + } } func ListModels(c *gin.Context) { @@ -142,21 +161,39 @@ func ListModels(c *gin.Context) { } models := model.GetGroupModels(user.Group) userOpenAiModels := make([]OpenAIModels, 0) + permission := getPermission() for _, s := range models { if _, ok := openAIModelsMap[s]; ok { userOpenAiModels = append(userOpenAiModels, openAIModelsMap[s]) + } else { + userOpenAiModels = append(userOpenAiModels, OpenAIModels{ + Id: s, + Object: "model", + Created: 1626777600, + OwnedBy: "openai", + Permission: permission, + Root: s, + Parent: nil, + }) } } c.JSON(200, gin.H{ - "object": "list", - "data": userOpenAiModels, + "success": true, + "data": userOpenAiModels, }) } func ChannelListModels(c *gin.Context) { c.JSON(200, gin.H{ - "object": "list", - "data": openAIModels, + "success": true, + "data": openAIModels, + }) +} + +func DashboardListModels(c *gin.Context) { + c.JSON(200, gin.H{ + "success": true, + "data": channelId2Models, }) } diff --git a/model/log.go b/model/log.go index 2740c5a2..57c64d0e 100644 --- a/model/log.go +++ b/model/log.go @@ -24,6 +24,7 @@ type Log struct { IsStream bool `json:"is_stream" gorm:"default:false"` ChannelId int `json:"channel" gorm:"index"` TokenId int `json:"token_id" gorm:"default:0;index"` + Other string `json:"other"` } const ( @@ -57,12 +58,13 @@ func RecordLog(userId int, logType int, content string) { } } -func RecordConsumeLog(ctx context.Context, userId int, channelId int, promptTokens int, completionTokens int, modelName string, tokenName string, quota int, content string, tokenId int, userQuota int, useTimeSeconds int, isStream bool) { +func RecordConsumeLog(ctx context.Context, userId int, channelId int, promptTokens int, completionTokens int, modelName string, tokenName string, quota int, content string, tokenId int, userQuota int, useTimeSeconds int, isStream bool, other map[string]interface{}) { common.LogInfo(ctx, fmt.Sprintf("record consume log: userId=%d, 用户调用前余额=%d, channelId=%d, promptTokens=%d, completionTokens=%d, modelName=%s, tokenName=%s, quota=%d, content=%s", userId, userQuota, channelId, promptTokens, completionTokens, modelName, tokenName, quota, content)) if !common.LogConsumeEnabled { return } username, _ := CacheGetUsername(userId) + otherStr := common.MapToJsonStr(other) log := &Log{ UserId: userId, Username: username, @@ -78,6 +80,7 @@ func RecordConsumeLog(ctx context.Context, userId int, channelId int, promptToke TokenId: tokenId, UseTime: useTimeSeconds, IsStream: isStream, + Other: otherStr, } err := DB.Create(log).Error if err != nil { diff --git a/model/option.go b/model/option.go index 1adc84cb..bfa7ddc9 100644 --- a/model/option.go +++ b/model/option.go @@ -83,6 +83,7 @@ func InitOptionMap() { common.OptionMap["ModelRatio"] = common.ModelRatio2JSONString() common.OptionMap["ModelPrice"] = common.ModelPrice2JSONString() common.OptionMap["GroupRatio"] = common.GroupRatio2JSONString() + common.OptionMap["CompletionRatio"] = common.CompletionRatio2JSONString() common.OptionMap["TopUpLink"] = common.TopUpLink common.OptionMap["ChatLink"] = common.ChatLink common.OptionMap["ChatLink2"] = common.ChatLink2 @@ -290,6 +291,8 @@ func updateOptionMap(key string, value string) (err error) { err = common.UpdateModelRatioByJSONString(value) case "GroupRatio": err = common.UpdateGroupRatioByJSONString(value) + case "CompletionRatio": + err = common.UpdateCompletionRatioByJSONString(value) case "ModelPrice": err = common.UpdateModelPriceByJSONString(value) case "TopUpLink": diff --git a/model/user.go b/model/user.go index 3e7169ae..eab8ced6 100644 --- a/model/user.go +++ b/model/user.go @@ -253,14 +253,17 @@ func (user *User) Edit(updatePassword bool) error { } } newUser := *user - DB.First(&user, user.Id) - err = DB.Model(user).Updates(map[string]interface{}{ + updates := map[string]interface{}{ "username": newUser.Username, - "password": newUser.Password, "display_name": newUser.DisplayName, "group": newUser.Group, "quota": newUser.Quota, - }).Error + } + if updatePassword { + updates["password"] = newUser.Password + } + DB.First(&user, user.Id) + err = DB.Model(user).Updates(updates).Error if err == nil { if common.RedisEnabled { _ = common.RedisSet(fmt.Sprintf("user_group:%d", user.Id), user.Group, time.Duration(UserId2GroupCacheSeconds)*time.Second) diff --git a/relay/channel/ai360/constants.go b/relay/channel/ai360/constants.go index cfc3cb28..82698fa8 100644 --- a/relay/channel/ai360/constants.go +++ b/relay/channel/ai360/constants.go @@ -6,3 +6,5 @@ var ModelList = []string{ "embedding_s1_v1", "semantic_similarity_s1_v1", } + +var ChannelName = "ai360" diff --git a/relay/channel/ollama/constants.go b/relay/channel/ollama/constants.go index 970e9777..682626a2 100644 --- a/relay/channel/ollama/constants.go +++ b/relay/channel/ollama/constants.go @@ -1,5 +1,7 @@ package ollama -var ModelList []string +var ModelList = []string{ + "llama3-7b", +} var ChannelName = "ollama" diff --git a/relay/channel/openai/constant.go b/relay/channel/openai/constant.go index 91f4e510..8c560c7d 100644 --- a/relay/channel/openai/constant.go +++ b/relay/channel/openai/constant.go @@ -6,7 +6,7 @@ var ModelList = []string{ "gpt-3.5-turbo-instruct", "gpt-4", "gpt-4-0314", "gpt-4-0613", "gpt-4-1106-preview", "gpt-4-0125-preview", "gpt-4-32k", "gpt-4-32k-0314", "gpt-4-32k-0613", - "gpt-4-turbo-preview", + "gpt-4-turbo-preview", "gpt-4-turbo", "gpt-4-turbo-2024-04-09", "gpt-4-vision-preview", "text-embedding-ada-002", "text-embedding-3-small", "text-embedding-3-large", "text-curie-001", "text-babbage-001", "text-ada-001", "text-davinci-002", "text-davinci-003", diff --git a/relay/constant/api_type.go b/relay/constant/api_type.go index 7f11ae22..1bc8b47f 100644 --- a/relay/constant/api_type.go +++ b/relay/constant/api_type.go @@ -25,8 +25,18 @@ const ( ) func ChannelType2APIType(channelType int) int { - apiType := APITypeOpenAI + apiType := -1 switch channelType { + case common.ChannelTypeOpenAI: + apiType = APITypeOpenAI + case common.ChannelTypeAzure: + apiType = APITypeOpenAI + case common.ChannelTypeMoonshot: + apiType = APITypeOpenAI + case common.ChannelTypeLingYiWanWu: + apiType = APITypeOpenAI + case common.ChannelType360: + apiType = APITypeOpenAI case common.ChannelTypeAnthropic: apiType = APITypeAnthropic case common.ChannelTypeBaidu: diff --git a/relay/relay-audio.go b/relay/relay-audio.go index 09ac2a0c..ef89597d 100644 --- a/relay/relay-audio.go +++ b/relay/relay-audio.go @@ -196,7 +196,10 @@ func AudioHelper(c *gin.Context, relayMode int) *dto.OpenAIErrorWithStatusCode { if quota != 0 { tokenName := c.GetString("token_name") logContent := fmt.Sprintf("模型倍率 %.2f,分组倍率 %.2f", modelRatio, groupRatio) - model.RecordConsumeLog(ctx, userId, channelId, promptTokens, 0, audioRequest.Model, tokenName, quota, logContent, tokenId, userQuota, int(useTimeSeconds), false) + other := make(map[string]interface{}) + other["model_ratio"] = modelRatio + other["group_ratio"] = groupRatio + model.RecordConsumeLog(ctx, userId, channelId, promptTokens, 0, audioRequest.Model, tokenName, quota, logContent, tokenId, userQuota, int(useTimeSeconds), false, other) model.UpdateUserUsedQuotaAndRequestCount(userId, quota) channelId := c.GetInt("channel_id") model.UpdateChannelUsedQuota(channelId, quota) diff --git a/relay/relay-image.go b/relay/relay-image.go index ce072d16..7f8cd9e6 100644 --- a/relay/relay-image.go +++ b/relay/relay-image.go @@ -191,7 +191,10 @@ func RelayImageHelper(c *gin.Context, relayMode int) *dto.OpenAIErrorWithStatusC quality = "hd" } logContent := fmt.Sprintf("模型倍率 %.2f,分组倍率 %.2f, 大小 %s, 品质 %s", modelRatio, groupRatio, imageRequest.Size, quality) - model.RecordConsumeLog(ctx, userId, channelId, 0, 0, imageRequest.Model, tokenName, quota, logContent, tokenId, userQuota, int(useTimeSeconds), false) + other := make(map[string]interface{}) + other["model_ratio"] = modelRatio + other["group_ratio"] = groupRatio + model.RecordConsumeLog(ctx, userId, channelId, 0, 0, imageRequest.Model, tokenName, quota, logContent, tokenId, userQuota, int(useTimeSeconds), false, other) model.UpdateUserUsedQuotaAndRequestCount(userId, quota) channelId := c.GetInt("channel_id") model.UpdateChannelUsedQuota(channelId, quota) diff --git a/relay/relay-mj.go b/relay/relay-mj.go index 27b4c6d3..16ad4121 100644 --- a/relay/relay-mj.go +++ b/relay/relay-mj.go @@ -202,7 +202,10 @@ func RelaySwapFace(c *gin.Context) *dto.MidjourneyResponse { if quota != 0 { tokenName := c.GetString("token_name") logContent := fmt.Sprintf("模型固定价格 %.2f,分组倍率 %.2f,操作 %s", modelPrice, groupRatio, constant.MjActionSwapFace) - model.RecordConsumeLog(ctx, userId, channelId, 0, 0, modelName, tokenName, quota, logContent, tokenId, userQuota, 0, false) + other := make(map[string]interface{}) + other["model_price"] = modelPrice + other["group_ratio"] = groupRatio + model.RecordConsumeLog(ctx, userId, channelId, 0, 0, modelName, tokenName, quota, logContent, tokenId, userQuota, 0, false, other) model.UpdateUserUsedQuotaAndRequestCount(userId, quota) channelId := c.GetInt("channel_id") model.UpdateChannelUsedQuota(channelId, quota) @@ -498,7 +501,10 @@ func RelayMidjourneySubmit(c *gin.Context, relayMode int) *dto.MidjourneyRespons if quota != 0 { tokenName := c.GetString("token_name") logContent := fmt.Sprintf("模型固定价格 %.2f,分组倍率 %.2f,操作 %s", modelPrice, groupRatio, midjRequest.Action) - model.RecordConsumeLog(ctx, userId, channelId, 0, 0, modelName, tokenName, quota, logContent, tokenId, userQuota, 0, false) + other := make(map[string]interface{}) + other["model_price"] = modelPrice + other["group_ratio"] = groupRatio + model.RecordConsumeLog(ctx, userId, channelId, 0, 0, modelName, tokenName, quota, logContent, tokenId, userQuota, 0, false, other) model.UpdateUserUsedQuotaAndRequestCount(userId, quota) channelId := c.GetInt("channel_id") model.UpdateChannelUsedQuota(channelId, quota) diff --git a/relay/relay-text.go b/relay/relay-text.go index 7c2720ec..9010381d 100644 --- a/relay/relay-text.go +++ b/relay/relay-text.go @@ -315,7 +315,12 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, textRe logModel = "gpt-4-gizmo-*" logContent += fmt.Sprintf(",模型 %s", textRequest.Model) } - model.RecordConsumeLog(ctx, relayInfo.UserId, relayInfo.ChannelId, promptTokens, completionTokens, logModel, tokenName, quota, logContent, relayInfo.TokenId, userQuota, int(useTimeSeconds), relayInfo.IsStream) + other := make(map[string]interface{}) + other["model_ratio"] = modelRatio + other["group_ratio"] = groupRatio + other["completion_ratio"] = completionRatio + other["model_price"] = modelPrice + model.RecordConsumeLog(ctx, relayInfo.UserId, relayInfo.ChannelId, promptTokens, completionTokens, logModel, tokenName, quota, logContent, relayInfo.TokenId, userQuota, int(useTimeSeconds), relayInfo.IsStream, other) //if quota != 0 { // diff --git a/router/api-router.go b/router/api-router.go index 85474543..8c0ae30c 100644 --- a/router/api-router.go +++ b/router/api-router.go @@ -14,6 +14,7 @@ func SetApiRouter(router *gin.Engine) { apiRouter.Use(middleware.GlobalAPIRateLimit()) { apiRouter.GET("/status", controller.GetStatus) + apiRouter.GET("/models", middleware.UserAuth(), controller.DashboardListModels) apiRouter.GET("/status/test", middleware.AdminAuth(), controller.TestStatus) apiRouter.GET("/notice", controller.GetNotice) apiRouter.GET("/about", controller.GetAbout) diff --git a/service/channel.go b/service/channel.go index 82ffd771..41ca6b77 100644 --- a/service/channel.go +++ b/service/channel.go @@ -63,7 +63,7 @@ func ShouldDisableChannel(err *relaymodel.OpenAIError, statusCode int) bool { return false } -func ShouldEnableChannel(err error, openAIErr *relaymodel.OpenAIError) bool { +func ShouldEnableChannel(err error, openAIErr *relaymodel.OpenAIError, status int) bool { if !common.AutomaticEnableChannelEnabled { return false } @@ -73,5 +73,8 @@ func ShouldEnableChannel(err error, openAIErr *relaymodel.OpenAIError) bool { if openAIErr != nil { return false } + if status != common.ChannelStatusAutoDisabled { + return false + } return true } diff --git a/web/src/components/ChannelsTable.js b/web/src/components/ChannelsTable.js index ebfcf4df..452309c3 100644 --- a/web/src/components/ChannelsTable.js +++ b/web/src/components/ChannelsTable.js @@ -31,6 +31,7 @@ import { } from '@douyinfe/semi-ui'; import EditChannel from '../pages/Channel/EditChannel'; import { IconTreeTriangleDown } from '@douyinfe/semi-icons'; +import { loadChannelModels } from './utils.js'; function renderTimestamp(timestamp) { return <>{timestamp2string(timestamp)}; @@ -354,27 +355,29 @@ const ChannelsTable = () => { }; const copySelectedChannel = async (id) => { - const channelToCopy = channels.find(channel => String(channel.id) === String(id)); - console.log(channelToCopy) + 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; + 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); - } + 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); + showError('渠道复制失败: ' + error.message); } }; @@ -395,6 +398,7 @@ const ChannelsTable = () => { showError(reason); }); fetchGroups().then(); + loadChannelModels().then(); }, []); const manageChannel = async (id, action, record, value) => { diff --git a/web/src/components/LogsTable.js b/web/src/components/LogsTable.js index 9331fa7b..0de36325 100644 --- a/web/src/components/LogsTable.js +++ b/web/src/components/LogsTable.js @@ -19,9 +19,15 @@ import { Spin, Table, Tag, + Tooltip, } from '@douyinfe/semi-ui'; import { ITEMS_PER_PAGE } from '../constants'; -import { renderNumber, renderQuota, stringToColor } from '../helpers/render'; +import { + renderModelPrice, + renderNumber, + renderQuota, + stringToColor, +} from '../helpers/render'; import Paragraph from '@douyinfe/semi-ui/lib/es/typography/paragraph'; const { Header } = Layout; @@ -292,16 +298,44 @@ const LogsTable = () => { title: '详情', dataIndex: 'content', render: (text, record, index) => { + if (record.other === '') { + return ( + + {text} + + ); + } + let other = JSON.parse(record.other); + let content = renderModelPrice( + other.model_ratio, + other.model_price, + other.completion_ratio, + other.group_ratio, + ); return ( - - {text} - + + + {text} + + ); }, }, diff --git a/web/src/components/MjLogsTable.js b/web/src/components/MjLogsTable.js index f22ddae5..e86ee313 100644 --- a/web/src/components/MjLogsTable.js +++ b/web/src/components/MjLogsTable.js @@ -236,6 +236,31 @@ const renderTimestamp = (timestampInSeconds) => { return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; // 格式化输出 }; +// 修改renderDuration函数以包含颜色逻辑 +function renderDuration(submit_time, finishTime) { + // 确保startTime和finishTime都是有效的时间戳 + if (!submit_time || !finishTime) return 'N/A'; + + // 将时间戳转换为Date对象 + const start = new Date(submit_time); + const finish = new Date(finishTime); + + // 计算时间差(毫秒) + const durationMs = finish - start; + + // 将时间差转换为秒,并保留一位小数 + const durationSec = (durationMs / 1000).toFixed(1); + + // 设置颜色:大于60秒则为红色,小于等于60秒则为绿色 + const color = durationSec > 60 ? 'red' : 'green'; + + // 返回带有样式的颜色标签 + return ( + + {durationSec} 秒 + + ); +} const LogsTable = () => { const [isModalOpen, setIsModalOpen] = useState(false); @@ -248,6 +273,15 @@ const LogsTable = () => { return
{renderTimestamp(text / 1000)}
; }, }, + { + title: '花费时间', + dataIndex: 'finish_time', // 以finish_time作为dataIndex + key: 'finish_time', + render: (finish, record) => { + // 假设record.start_time是存在的,并且finish是完成时间的时间戳 + return renderDuration(record.submit_time, finish); + }, + }, { title: '渠道', dataIndex: 'channel_id', diff --git a/web/src/components/OperationSetting.js b/web/src/components/OperationSetting.js index 2dd5489b..31345a5d 100644 --- a/web/src/components/OperationSetting.js +++ b/web/src/components/OperationSetting.js @@ -29,6 +29,7 @@ const OperationSetting = () => { PreConsumedQuota: 0, StreamCacheQueueLength: 0, ModelRatio: '', + CompletionRatio: '', ModelPrice: '', GroupRatio: '', TopUpLink: '', @@ -69,6 +70,7 @@ const OperationSetting = () => { if ( item.key === 'ModelRatio' || item.key === 'GroupRatio' || + item.key === 'CompletionRatio' || item.key === 'ModelPrice' ) { item.value = JSON.stringify(JSON.parse(item.value), null, 2); @@ -166,6 +168,13 @@ const OperationSetting = () => { } await updateOption('ModelRatio', inputs.ModelRatio); } + if (originInputs['CompletionRatio'] !== inputs.CompletionRatio) { + if (!verifyJSON(inputs.CompletionRatio)) { + showError('模型补全倍率不是合法的 JSON 字符串'); + return; + } + await updateOption('CompletionRatio', inputs.CompletionRatio); + } if (originInputs['GroupRatio'] !== inputs.GroupRatio) { if (!verifyJSON(inputs.GroupRatio)) { showError('分组倍率不是合法的 JSON 字符串'); @@ -304,6 +313,20 @@ const OperationSetting = () => { placeholder='为一个 JSON 文本,键为模型名称,值为倍率' /> + + + { const [customModel, setCustomModel] = useState(''); const handleInputChange = (name, value) => { setInputs((inputs) => ({ ...inputs, [name]: value })); - if (name === 'type' && inputs.models.length === 0) { + if (name === 'type') { let localModels = []; switch (value) { - case 33: - case 14: - localModels = [ - 'claude-instant-1.2', - 'claude-2', - 'claude-2.0', - 'claude-2.1', - 'claude-3-opus-20240229', - 'claude-3-sonnet-20240229', - 'claude-3-haiku-20240307', - ]; - break; - case 11: - localModels = ['PaLM-2']; - break; - case 15: - localModels = [ - 'ERNIE-Bot', - 'ERNIE-Bot-turbo', - 'ERNIE-Bot-4', - 'Embedding-V1', - ]; - break; - case 17: - localModels = [ - 'qwen-turbo', - 'qwen-plus', - 'qwen-max', - 'qwen-max-longcontext', - 'text-embedding-v1', - ]; - break; - case 16: - localModels = ['chatglm_pro', 'chatglm_std', 'chatglm_lite']; - break; - case 18: - localModels = [ - 'SparkDesk', - 'SparkDesk-v1.1', - 'SparkDesk-v2.1', - 'SparkDesk-v3.1', - 'SparkDesk-v3.5', - ]; - break; - case 19: - localModels = [ - '360GPT_S2_V9', - 'embedding-bert-512-v1', - 'embedding_s1_v1', - 'semantic_similarity_s1_v1', - ]; - break; - case 23: - localModels = ['hunyuan']; - break; - case 24: - 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 34: - localModels = [ - 'command-r', - 'command-r-plus', - 'command-light', - 'command-light-nightly', - 'command', - 'command-nightly', - ]; - break; - case 25: - localModels = [ - 'moonshot-v1-8k', - 'moonshot-v1-32k', - 'moonshot-v1-128k', - ]; - break; - case 26: - localModels = ['glm-4', 'glm-4v', 'glm-3-turbo']; - break; - case 31: - localModels = ['yi-34b-chat-0205', 'yi-34b-chat-200k', 'yi-vl-plus']; - break; case 2: localModels = [ 'mj_imagine', @@ -207,8 +120,14 @@ const EditChannel = (props) => { 'mj_pan', ]; break; + default: + localModels = getChannelModels(value); + break; } - setInputs((inputs) => ({ ...inputs, models: localModels })); + if (inputs.models.length === 0) { + setInputs((inputs) => ({ ...inputs, models: localModels })); + } + setBasicModels(localModels); } //setAutoBan }; @@ -244,6 +163,7 @@ const EditChannel = (props) => { } else { setAutoBan(true); } + setBasicModels(getChannelModels(data.type)); // console.log(data); } else { showError(message); @@ -312,6 +232,9 @@ const EditChannel = (props) => { loadChannel().then(() => {}); } else { setInputs(originInputs); + let localModels = getChannelModels(inputs.type); + setBasicModels(localModels); + setInputs((inputs) => ({ ...inputs, models: localModels })); } }, [props.editingChannel.id]); @@ -596,7 +519,7 @@ const EditChannel = (props) => { handleInputChange('models', basicModels); }} > - 填入基础模型 + 填入相关模型 + )} 以下信息不可修改 @@ -245,6 +261,30 @@ const EditUser = (props) => { /> + { + addLocalQuota(); + setIsModalOpen(false); + }} + onCancel={() => setIsModalOpen(false)} + closable={null} + > +
+ {`新额度${renderQuota(quota)} + ${renderQuota(addQuotaLocal)} = ${renderQuota(quota + parseInt(addQuotaLocal))}`} +
+ { + setAddQuotaLocal(value); + }} + value={addQuotaLocal} + type={'number'} + autoComplete='new-password' + /> +
); };