diff --git a/api/core/types/chat.go b/api/core/types/chat.go index 175cd3ab..83742852 100644 --- a/api/core/types/chat.go +++ b/api/core/types/chat.go @@ -57,7 +57,7 @@ type ChatModel struct { Id uint `json:"id"` Platform Platform `json:"platform"` Value string `json:"value"` - Weight int `json:"weight"` + Power int `json:"power"` } type ApiError struct { @@ -92,3 +92,21 @@ func GetModelMaxToken(model string) int { } return 4096 } + +// PowerType 算力日志类型 +type PowerType int + +const ( + PowerRecharge = PowerType(1) // 充值 + PowerConsume = PowerType(2) // 消费 + PowerRefund = PowerType(3) // 任务(SD,MJ)执行失败,退款 + PowerInvite = PowerType(4) // 邀请奖励 + PowerReward = PowerType(5) // 众筹 +) + +type PowerMark int + +const ( + PowerSub = PowerMark(0) + PowerAdd = PowerMark(1) +) diff --git a/api/core/types/config.go b/api/core/types/config.go index bb71dd8d..a31d8820 100644 --- a/api/core/types/config.go +++ b/api/core/types/config.go @@ -157,8 +157,7 @@ type UserChatConfig struct { } type InviteReward struct { - ChatCalls int `json:"chat_calls"` - ImgCalls int `json:"img_calls"` + Power int `json:"power"` } type ModelAPIConfig struct { @@ -167,26 +166,26 @@ type ModelAPIConfig struct { } type SystemConfig struct { - Title string `json:"title"` - AdminTitle string `json:"admin_title"` - InitChatCalls int `json:"init_chat_calls"` // 新用户注册赠送对话次数 - InitImgCalls int `json:"init_img_calls"` // 新用户注册赠送绘图次数 - VipMonthCalls int `json:"vip_month_calls"` // VIP 会员每月赠送的对话次数 - VipMonthImgCalls int `json:"vip_month_img_calls"` // VIP 会员每月赠送绘图次数 + Title string `json:"title"` + AdminTitle string `json:"admin_title"` + InitPower int `json:"init_power"` // 新用户注册赠送算力值 RegisterWays []string `json:"register_ways"` // 注册方式:支持手机,邮箱注册 EnabledRegister bool `json:"enabled_register"` // 是否开放注册 - RewardImg string `json:"reward_img"` // 众筹收款二维码地址 - EnabledReward bool `json:"enabled_reward"` // 启用众筹功能 - ChatCallPrice float64 `json:"chat_call_price"` // 对话单次调用费用 - ImgCallPrice float64 `json:"img_call_price"` // 绘图单次调用费用 + RewardImg string `json:"reward_img"` // 众筹收款二维码地址 + EnabledReward bool `json:"enabled_reward"` // 启用众筹功能 + PowerPrice float64 `json:"power_price"` // 算力单价 OrderPayTimeout int `json:"order_pay_timeout"` //订单支付超时时间 DefaultModels []string `json:"default_models"` // 默认开通的 AI 模型 OrderPayInfoText string `json:"order_pay_info_text"` // 订单支付页面说明文字 - InviteChatCalls int `json:"invite_chat_calls"` // 邀请用户注册奖励对话次数 - InviteImgCalls int `json:"invite_img_calls"` // 邀请用户注册奖励绘图次数 + InvitePower int `json:"invite_power"` // 邀请新用户赠送算力值 + VipMonthPower int `json:"vip_month_power"` // VIP 会员每月赠送的算力值 + + MjPower int `json:"mj_power"` // MJ 绘画消耗算力 + SdPower int `json:"sd_power"` // SD 绘画消耗算力 + DallPower int `json:"dall_power"` // DALLE3 绘图消耗算力 WechatCardURL string `json:"wechat_card_url"` // 微信客服地址 } diff --git a/api/core/types/order.go b/api/core/types/order.go index 55c19a1c..78a7c40b 100644 --- a/api/core/types/order.go +++ b/api/core/types/order.go @@ -9,10 +9,9 @@ const ( ) type OrderRemark struct { - Days int `json:"days"` // 有效期 - Calls int `json:"calls"` // 增加对话次数 - ImgCalls int `json:"img_calls"` // 增加绘图次数 - Name string `json:"name"` // 产品名称 + Days int `json:"days"` // 有效期 + Power int `json:"power"` // 增加算力点数 + Name string `json:"name"` // 产品名称 Price float64 `json:"price"` Discount float64 `json:"discount"` } diff --git a/api/handler/admin/chat_model_handler.go b/api/handler/admin/chat_model_handler.go index 5aa83f0f..2a9e41b5 100644 --- a/api/handler/admin/chat_model_handler.go +++ b/api/handler/admin/chat_model_handler.go @@ -48,7 +48,7 @@ func (h *ChatModelHandler) Save(c *gin.Context) { Enabled: data.Enabled, SortNum: data.SortNum, Open: data.Open, - Weight: data.Weight} + Power: data.Weight} item.Id = data.Id if item.Id > 0 { item.CreatedAt = time.Unix(data.CreatedAt, 0) diff --git a/api/handler/admin/product_handler.go b/api/handler/admin/product_handler.go index 11fa66e5..08e3ac11 100644 --- a/api/handler/admin/product_handler.go +++ b/api/handler/admin/product_handler.go @@ -32,8 +32,7 @@ func (h *ProductHandler) Save(c *gin.Context) { Discount float64 `json:"discount"` Enabled bool `json:"enabled"` Days int `json:"days"` - Calls int `json:"calls"` - ImgCalls int `json:"img_calls"` + Power int `json:"power"` CreatedAt int64 `json:"created_at"` } if err := c.ShouldBindJSON(&data); err != nil { @@ -46,8 +45,7 @@ func (h *ProductHandler) Save(c *gin.Context) { Price: data.Price, Discount: data.Discount, Days: data.Days, - Calls: data.Calls, - ImgCalls: data.ImgCalls, + Power: data.Power, Enabled: data.Enabled} item.Id = data.Id if item.Id > 0 { diff --git a/api/handler/admin/user_handler.go b/api/handler/admin/user_handler.go index eb1d8168..1366bff4 100644 --- a/api/handler/admin/user_handler.go +++ b/api/handler/admin/user_handler.go @@ -66,8 +66,6 @@ func (h *UserHandler) Save(c *gin.Context) { Id uint `json:"id"` Password string `json:"password"` Username string `json:"username"` - Calls int `json:"calls"` - ImgCalls int `json:"img_calls"` ChatRoles []string `json:"chat_roles"` ChatModels []string `json:"chat_models"` ExpiredTime string `json:"expired_time"` @@ -86,8 +84,6 @@ func (h *UserHandler) Save(c *gin.Context) { // 此处需要用 map 更新,用结构体无法更新 0 值 res = h.db.Model(&user).Updates(map[string]interface{}{ "username": data.Username, - "calls": data.Calls, - "img_calls": data.ImgCalls, "status": data.Status, "vip": data.Vip, "chat_roles_json": utils.JsonEncode(data.ChatRoles), @@ -113,8 +109,6 @@ func (h *UserHandler) Save(c *gin.Context) { types.ChatGLM: "", }, }), - Calls: data.Calls, - ImgCalls: data.ImgCalls, } res = h.db.Create(&u) _ = utils.CopyObject(u, &userVo) diff --git a/api/handler/chatimpl/azure_handler.go b/api/handler/chatimpl/azure_handler.go index 82a96d18..87bd4599 100644 --- a/api/handler/chatimpl/azure_handler.go +++ b/api/handler/chatimpl/azure_handler.go @@ -103,8 +103,6 @@ func (h *ChatHandler) sendAzureMessage( // 消息发送成功 if len(contents) > 0 { - // 更新用户的对话次数 - h.subUserCalls(userVo, session) if message.Role == "" { message.Role = "assistant" @@ -145,8 +143,8 @@ func (h *ChatHandler) sendAzureMessage( } // 计算本次对话消耗的总 token 数量 - totalTokens, _ := utils.CalcTokens(message.Content, req.Model) - totalTokens += getTotalTokens(req) + replyTokens, _ := utils.CalcTokens(message.Content, req.Model) + replyTokens += getTotalTokens(req) historyReplyMsg := model.ChatMessage{ UserId: userVo.Id, @@ -155,7 +153,7 @@ func (h *ChatHandler) sendAzureMessage( Type: types.ReplyMsg, Icon: role.Icon, Content: message.Content, - Tokens: totalTokens, + Tokens: replyTokens, UseContext: true, Model: req.Model, } @@ -166,8 +164,8 @@ func (h *ChatHandler) sendAzureMessage( logger.Error("failed to save reply history message: ", res.Error) } - // 更新用户信息 - h.incUserTokenFee(userVo.Id, totalTokens) + // 更新用户算力 + h.subUserPower(userVo, session, promptToken, replyTokens) } // 保存当前会话 diff --git a/api/handler/chatimpl/baidu_handler.go b/api/handler/chatimpl/baidu_handler.go index bf50feeb..7c227110 100644 --- a/api/handler/chatimpl/baidu_handler.go +++ b/api/handler/chatimpl/baidu_handler.go @@ -128,9 +128,6 @@ func (h *ChatHandler) sendBaiduMessage( // 消息发送成功 if len(contents) > 0 { - // 更新用户的对话次数 - h.subUserCalls(userVo, session) - if message.Role == "" { message.Role = "assistant" } @@ -171,8 +168,8 @@ func (h *ChatHandler) sendBaiduMessage( // for reply // 计算本次对话消耗的总 token 数量 - replyToken, _ := utils.CalcTokens(message.Content, req.Model) - totalTokens := replyToken + getTotalTokens(req) + replyTokens, _ := utils.CalcTokens(message.Content, req.Model) + totalTokens := replyTokens + getTotalTokens(req) historyReplyMsg := model.ChatMessage{ UserId: userVo.Id, ChatId: session.ChatId, @@ -190,8 +187,8 @@ func (h *ChatHandler) sendBaiduMessage( if res.Error != nil { logger.Error("failed to save reply history message: ", res.Error) } - // 更新用户信息 - h.incUserTokenFee(userVo.Id, totalTokens) + // 更新用户算力 + h.subUserPower(userVo, session, promptToken, replyTokens) } // 保存当前会话 diff --git a/api/handler/chatimpl/chat_handler.go b/api/handler/chatimpl/chat_handler.go index b7b1bc14..8327abe2 100644 --- a/api/handler/chatimpl/chat_handler.go +++ b/api/handler/chatimpl/chat_handler.go @@ -111,7 +111,7 @@ func (h *ChatHandler) ChatHandle(c *gin.Context) { session.Model = types.ChatModel{ Id: chatModel.Id, Value: chatModel.Value, - Weight: chatModel.Weight, + Power: chatModel.Power, Platform: types.Platform(chatModel.Platform)} logger.Infof("New websocket connected, IP: %s, Username: %s", c.ClientIP(), session.Username) var chatRole model.ChatRole @@ -207,13 +207,13 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio return nil } - if userVo.Calls < session.Model.Weight { - utils.ReplyMessage(ws, fmt.Sprintf("您当前剩余对话次数(%d)已不足以支付当前模型的单次对话需要消耗的对话额度(%d)!", userVo.Calls, session.Model.Weight)) + if userVo.Power < session.Model.Power { + utils.ReplyMessage(ws, fmt.Sprintf("您当前剩余对话次数(%d)已不足以支付当前模型的单次对话需要消耗的对话额度(%d)!", userVo.Power, session.Model.Power)) utils.ReplyMessage(ws, ErrImg) return nil } - if userVo.Calls <= 0 && userVo.ChatConfig.ApiKeys[session.Model.Platform] == "" { + if userVo.Power <= 0 && userVo.ChatConfig.ApiKeys[session.Model.Platform] == "" { utils.ReplyMessage(ws, "您的对话次数已经用尽,请联系管理员或者充值点卡继续对话!") utils.ReplyMessage(ws, ErrImg) return nil @@ -533,23 +533,28 @@ func (h *ChatHandler) doRequest(ctx context.Context, req types.ApiRequest, platf return client.Do(request) } -// 扣减用户的对话次数 -func (h *ChatHandler) subUserCalls(userVo vo.User, session *types.ChatSession) { - // 仅当用户没有导入自己的 API KEY 时才进行扣减 - if userVo.ChatConfig.ApiKeys[session.Model.Platform] == "" { - num := 1 - if session.Model.Weight > 0 { - num = session.Model.Weight - } - h.db.Model(&model.User{}).Where("id = ?", userVo.Id).UpdateColumn("calls", gorm.Expr("calls - ?", num)) +// 扣减用户算力 +func (h *ChatHandler) subUserPower(userVo vo.User, session *types.ChatSession, promptTokens int, replyTokens int) { + power := 1 + if session.Model.Power > 0 { + power = session.Model.Power + } + res := h.db.Model(&model.User{}).Where("id = ?", userVo.Id).UpdateColumn("power", gorm.Expr("power - ?", power)) + if res.Error == nil { + // 记录算力消费日志 + h.db.Debug().Create(&model.PowerLog{ + UserId: userVo.Id, + Username: userVo.Username, + Type: types.PowerConsume, + Amount: power, + Mark: types.PowerSub, + Balance: userVo.Power - power, + Model: session.Model.Value, + Remark: fmt.Sprintf("提问长度:%d,回复长度:%d", promptTokens, replyTokens), + CreatedAt: time.Now(), + }) } -} -func (h *ChatHandler) incUserTokenFee(userId uint, tokens int) { - h.db.Model(&model.User{}).Where("id = ?", userId). - UpdateColumn("total_tokens", gorm.Expr("total_tokens + ?", tokens)) - h.db.Model(&model.User{}).Where("id = ?", userId). - UpdateColumn("tokens", gorm.Expr("tokens + ?", tokens)) } // 将AI回复消息中生成的图片链接下载到本地 diff --git a/api/handler/chatimpl/chatglm_handler.go b/api/handler/chatimpl/chatglm_handler.go index 96329c3c..3db444b8 100644 --- a/api/handler/chatimpl/chatglm_handler.go +++ b/api/handler/chatimpl/chatglm_handler.go @@ -107,9 +107,6 @@ func (h *ChatHandler) sendChatGLMMessage( // 消息发送成功 if len(contents) > 0 { - // 更新用户的对话次数 - h.subUserCalls(userVo, session) - if message.Role == "" { message.Role = "assistant" } @@ -150,8 +147,8 @@ func (h *ChatHandler) sendChatGLMMessage( // for reply // 计算本次对话消耗的总 token 数量 - replyToken, _ := utils.CalcTokens(message.Content, req.Model) - totalTokens := replyToken + getTotalTokens(req) + replyTokens, _ := utils.CalcTokens(message.Content, req.Model) + totalTokens := replyTokens + getTotalTokens(req) historyReplyMsg := model.ChatMessage{ UserId: userVo.Id, ChatId: session.ChatId, @@ -169,8 +166,9 @@ func (h *ChatHandler) sendChatGLMMessage( if res.Error != nil { logger.Error("failed to save reply history message: ", res.Error) } - // 更新用户信息 - h.incUserTokenFee(userVo.Id, totalTokens) + + // 更新用户算力 + h.subUserPower(userVo, session, promptToken, replyTokens) } // 保存当前会话 diff --git a/api/handler/chatimpl/openai_handler.go b/api/handler/chatimpl/openai_handler.go index 888f7f40..13df6707 100644 --- a/api/handler/chatimpl/openai_handler.go +++ b/api/handler/chatimpl/openai_handler.go @@ -173,9 +173,6 @@ func (h *ChatHandler) sendOpenAiMessage( // 消息发送成功 if len(contents) > 0 { - // 更新用户的对话次数 - h.subUserCalls(userVo, session) - if message.Role == "" { message.Role = "assistant" } @@ -220,16 +217,16 @@ func (h *ChatHandler) sendOpenAiMessage( } // 计算本次对话消耗的总 token 数量 - var totalTokens = 0 + var replyTokens = 0 if toolCall { // prompt + 函数名 + 参数 token tokens, _ := utils.CalcTokens(function.Name, req.Model) - totalTokens += tokens + replyTokens += tokens tokens, _ = utils.CalcTokens(utils.InterfaceToString(arguments), req.Model) - totalTokens += tokens + replyTokens += tokens } else { - totalTokens, _ = utils.CalcTokens(message.Content, req.Model) + replyTokens, _ = utils.CalcTokens(message.Content, req.Model) } - totalTokens += getTotalTokens(req) + replyTokens += getTotalTokens(req) historyReplyMsg := model.ChatMessage{ UserId: userVo.Id, @@ -238,7 +235,7 @@ func (h *ChatHandler) sendOpenAiMessage( Type: types.ReplyMsg, Icon: role.Icon, Content: h.extractImgUrl(message.Content), - Tokens: totalTokens, + Tokens: replyTokens, UseContext: useContext, Model: req.Model, } @@ -249,8 +246,8 @@ func (h *ChatHandler) sendOpenAiMessage( logger.Error("failed to save reply history message: ", res.Error) } - // 更新用户信息 - h.incUserTokenFee(userVo.Id, totalTokens) + // 更新用户算力 + h.subUserPower(userVo, session, promptToken, replyTokens) } // 保存当前会话 diff --git a/api/handler/chatimpl/qwen_handler.go b/api/handler/chatimpl/qwen_handler.go index ccfd8fa4..0a6b7e9e 100644 --- a/api/handler/chatimpl/qwen_handler.go +++ b/api/handler/chatimpl/qwen_handler.go @@ -128,9 +128,6 @@ func (h *ChatHandler) sendQWenMessage( // 消息发送成功 if len(contents) > 0 { - // 更新用户的对话次数 - h.subUserCalls(userVo, session) - if message.Role == "" { message.Role = "assistant" } @@ -171,8 +168,8 @@ func (h *ChatHandler) sendQWenMessage( // for reply // 计算本次对话消耗的总 token 数量 - replyToken, _ := utils.CalcTokens(message.Content, req.Model) - totalTokens := replyToken + getTotalTokens(req) + replyTokens, _ := utils.CalcTokens(message.Content, req.Model) + totalTokens := replyTokens + getTotalTokens(req) historyReplyMsg := model.ChatMessage{ UserId: userVo.Id, ChatId: session.ChatId, @@ -190,8 +187,9 @@ func (h *ChatHandler) sendQWenMessage( if res.Error != nil { logger.Error("failed to save reply history message: ", res.Error) } - // 更新用户信息 - h.incUserTokenFee(userVo.Id, totalTokens) + + // 更新用户算力 + h.subUserPower(userVo, session, promptToken, replyTokens) } // 保存当前会话 diff --git a/api/handler/chatimpl/xunfei_handler.go b/api/handler/chatimpl/xunfei_handler.go index 880dde1c..c29d1fd1 100644 --- a/api/handler/chatimpl/xunfei_handler.go +++ b/api/handler/chatimpl/xunfei_handler.go @@ -166,9 +166,6 @@ func (h *ChatHandler) sendXunFeiMessage( // 消息发送成功 if len(contents) > 0 { - // 更新用户的对话次数 - h.subUserCalls(userVo, session) - if message.Role == "" { message.Role = "assistant" } @@ -209,8 +206,8 @@ func (h *ChatHandler) sendXunFeiMessage( // for reply // 计算本次对话消耗的总 token 数量 - replyToken, _ := utils.CalcTokens(message.Content, req.Model) - totalTokens := replyToken + getTotalTokens(req) + replyTokens, _ := utils.CalcTokens(message.Content, req.Model) + totalTokens := replyTokens + getTotalTokens(req) historyReplyMsg := model.ChatMessage{ UserId: userVo.Id, ChatId: session.ChatId, @@ -228,8 +225,9 @@ func (h *ChatHandler) sendXunFeiMessage( if res.Error != nil { logger.Error("failed to save reply history message: ", res.Error) } - // 更新用户信息 - h.incUserTokenFee(userVo.Id, totalTokens) + + // 更新用户算力 + h.subUserPower(userVo, session, promptToken, replyTokens) } // 保存当前会话 diff --git a/api/handler/function_handler.go b/api/handler/function_handler.go index b74985f2..061cc7c8 100644 --- a/api/handler/function_handler.go +++ b/api/handler/function_handler.go @@ -200,8 +200,8 @@ func (h *FunctionHandler) Dall3(c *gin.Context) { return } - if user.ImgCalls <= 0 { - resp.ERROR(c, "当前用户的绘图次数额度不足!") + if user.Power < h.App.SysConfig.DallPower { + resp.ERROR(c, "当前用户剩余算力不足以完成本次绘画!") return } @@ -275,7 +275,21 @@ func (h *FunctionHandler) Dall3(c *gin.Context) { content := fmt.Sprintf("下面是根据您的描述创作的图片,它描绘了 【%s】 的场景。 \n\n![](%s)\n", prompt, imgURL) // update user's img_calls - h.db.Model(&model.User{}).Where("id = ?", user.Id).UpdateColumn("img_calls", gorm.Expr("img_calls - ?", 1)) + tx = h.db.Model(&model.User{}).Where("id = ?", user.Id).UpdateColumn("power", gorm.Expr("power - ?", h.App.SysConfig.DallPower)) + // 记录算力变化日志 + if tx.Error == nil && tx.RowsAffected > 0 { + h.db.Create(&model.PowerLog{ + UserId: user.Id, + Username: user.Username, + Type: types.PowerConsume, + Amount: h.App.SysConfig.DallPower, + Balance: user.Power - h.App.SysConfig.DallPower, + Mark: types.PowerSub, + Model: "dall-e-3", + Remark: "", + CreatedAt: time.Now(), + }) + } resp.SUCCESS(c, content) } diff --git a/api/handler/mj_handler.go b/api/handler/mj_handler.go index 74a1cb0a..8f27e52e 100644 --- a/api/handler/mj_handler.go +++ b/api/handler/mj_handler.go @@ -48,8 +48,8 @@ func (h *MidJourneyHandler) preCheck(c *gin.Context) bool { return false } - if user.ImgCalls <= 0 { - resp.ERROR(c, "您的绘图次数不足,请联系管理员充值!") + if user.Power < h.App.SysConfig.MjPower { + resp.ERROR(c, "当前用户剩余算力不足以完成本次绘画!") return false } @@ -160,13 +160,18 @@ func (h *MidJourneyHandler) Image(c *gin.Context) { TaskId: taskId, Progress: 0, Prompt: prompt, + Power: h.App.SysConfig.MjPower, CreatedAt: time.Now(), } + opt := "绘图" if data.TaskType == types.TaskBlend.String() { - data.Prompt = "融图:" + strings.Join(data.ImgArr, ",") + job.Prompt = "融图:" + strings.Join(data.ImgArr, ",") + opt = "融图" } else if data.TaskType == types.TaskSwapFace.String() { - data.Prompt = "换脸:" + strings.Join(data.ImgArr, ",") + job.Prompt = "换脸:" + strings.Join(data.ImgArr, ",") + opt = "换脸" } + if res := h.db.Create(&job); res.Error != nil || res.RowsAffected == 0 { resp.ERROR(c, "添加任务失败:"+res.Error.Error()) return @@ -187,8 +192,23 @@ func (h *MidJourneyHandler) Image(c *gin.Context) { _ = client.Send([]byte("Task Updated")) } - // update user's img calls - h.db.Model(&model.User{}).Where("id = ?", job.UserId).UpdateColumn("img_calls", gorm.Expr("img_calls - ?", 1)) + // update user's power + tx := h.db.Model(&model.User{}).Where("id = ?", job.UserId).UpdateColumn("power", gorm.Expr("power - ?", job.Power)) + // 记录算力变化日志 + if tx.Error == nil && tx.RowsAffected > 0 { + user, _ := utils.GetLoginUser(c, h.db) + h.db.Create(&model.PowerLog{ + UserId: user.Id, + Username: user.Username, + Type: types.PowerConsume, + Amount: job.Power, + Balance: user.Power - job.Power, + Mark: types.PowerSub, + Model: "mid-journey", + Remark: fmt.Sprintf("%s操作,任务ID:%s", opt, job.TaskId), + CreatedAt: time.Now(), + }) + } resp.SUCCESS(c) } @@ -276,6 +296,7 @@ func (h *MidJourneyHandler) Variation(c *gin.Context) { TaskId: taskId, Progress: 0, Prompt: data.Prompt, + Power: h.App.SysConfig.MjPower, CreatedAt: time.Now(), } if res := h.db.Create(&job); res.Error != nil || res.RowsAffected == 0 { @@ -300,8 +321,23 @@ func (h *MidJourneyHandler) Variation(c *gin.Context) { _ = client.Send([]byte("Task Updated")) } - // update user's img calls - h.db.Model(&model.User{}).Where("id = ?", job.UserId).UpdateColumn("img_calls", gorm.Expr("img_calls - ?", 1)) + // update user's power + tx := h.db.Model(&model.User{}).Where("id = ?", job.UserId).UpdateColumn("power", gorm.Expr("power - ?", job.Power)) + // 记录算力变化日志 + if tx.Error == nil && tx.RowsAffected > 0 { + user, _ := utils.GetLoginUser(c, h.db) + h.db.Create(&model.PowerLog{ + UserId: user.Id, + Username: user.Username, + Type: types.PowerConsume, + Amount: job.Power, + Balance: user.Power - job.Power, + Mark: types.PowerSub, + Model: "mid-journey", + Remark: fmt.Sprintf("Variation 操作,任务ID:%s", job.TaskId), + CreatedAt: time.Now(), + }) + } resp.SUCCESS(c) } @@ -368,13 +404,6 @@ func (h *MidJourneyHandler) getData(finish bool, userId uint, page int, pageSize continue } - // 失败的任务直接删除 - if job.Progress == -1 { - h.db.Delete(&model.MidJourneyJob{Id: job.Id}) - jobs = append(jobs, job) - continue - } - if item.Progress < 100 && item.ImgURL == "" && item.OrgURL != "" { // discord 服务器图片需要使用代理转发图片数据流 if strings.HasPrefix(item.OrgURL, "https://cdn.discordapp.com") { diff --git a/api/handler/payment_handler.go b/api/handler/payment_handler.go index 797527b8..1601e687 100644 --- a/api/handler/payment_handler.go +++ b/api/handler/payment_handler.go @@ -202,8 +202,7 @@ func (h *PaymentHandler) PayQrcode(c *gin.Context) { // 创建订单 remark := types.OrderRemark{ Days: product.Days, - Calls: product.Calls, - ImgCalls: product.ImgCalls, + Power: product.Power, Name: product.Name, Price: product.Price, Discount: product.Discount, @@ -313,20 +312,16 @@ func (h *PaymentHandler) notify(orderNo string, tradeNo string) error { if remark.Days > 0 { // 只延期 VIP,不增加调用次数 user.ExpiredTime = time.Unix(user.ExpiredTime, 0).AddDate(0, 0, remark.Days).Unix() } else { // 充值点卡,直接增加次数即可 - user.Calls += remark.Calls - user.ImgCalls += remark.ImgCalls + user.Power += remark.Power } } else { // 非 VIP 用户 if remark.Days > 0 { // vip 套餐:days > 0, calls == 0 user.ExpiredTime = time.Now().AddDate(0, 0, remark.Days).Unix() - user.Calls += h.App.SysConfig.VipMonthCalls - user.ImgCalls += h.App.SysConfig.VipMonthImgCalls + user.Power += h.App.SysConfig.VipMonthPower user.Vip = true - } else { //点卡:days == 0, calls > 0 - user.Calls += remark.Calls - user.ImgCalls += remark.ImgCalls + user.Power += remark.Power } } diff --git a/api/handler/reward_handler.go b/api/handler/reward_handler.go index 8fc36905..6b6c819f 100644 --- a/api/handler/reward_handler.go +++ b/api/handler/reward_handler.go @@ -7,11 +7,13 @@ import ( "chatplus/store/vo" "chatplus/utils" "chatplus/utils/resp" + "fmt" "github.com/gin-gonic/gin" "gorm.io/gorm" "math" "strings" "sync" + "time" ) type RewardHandler struct { @@ -30,7 +32,6 @@ func NewRewardHandler(server *core.AppServer, db *gorm.DB) *RewardHandler { func (h *RewardHandler) Verify(c *gin.Context) { var data struct { TxId string `json:"tx_id"` - Type string `json:"type"` } if err := c.ShouldBindJSON(&data); err != nil { resp.ERROR(c, types.InvalidArgs) @@ -63,16 +64,11 @@ func (h *RewardHandler) Verify(c *gin.Context) { tx := h.db.Begin() exchange := vo.RewardExchange{} - if data.Type == "chat" { - calls := math.Ceil(item.Amount / h.App.SysConfig.ChatCallPrice) - exchange.Calls = int(calls) - res = h.db.Model(&user).UpdateColumn("calls", gorm.Expr("calls + ?", calls)) - } else if data.Type == "img" { - calls := math.Ceil(item.Amount / h.App.SysConfig.ImgCallPrice) - exchange.ImgCalls = int(calls) - res = h.db.Model(&user).UpdateColumn("img_calls", gorm.Expr("img_calls + ?", calls)) - } + power := math.Ceil(item.Amount / h.App.SysConfig.PowerPrice) + exchange.Power = int(power) + res = tx.Model(&user).UpdateColumn("power", gorm.Expr("power + ?", exchange.Power)) if res.Error != nil { + tx.Rollback() resp.ERROR(c, "更新数据库失败!") return } @@ -81,13 +77,25 @@ func (h *RewardHandler) Verify(c *gin.Context) { item.Status = true item.UserId = user.Id item.Exchange = utils.JsonEncode(exchange) - res = h.db.Updates(&item) + res = tx.Updates(&item) if res.Error != nil { tx.Rollback() resp.ERROR(c, "更新数据库失败!") return } + // 记录算力充值日志 + h.db.Create(&model.PowerLog{ + UserId: user.Id, + Username: user.Username, + Type: types.PowerReward, + Amount: exchange.Power, + Balance: user.Power + exchange.Power, + Mark: types.PowerAdd, + Model: "", + Remark: fmt.Sprintf("众筹充值算力,金额:%f,价格:%f", item.Amount, h.App.SysConfig.PowerPrice), + CreatedAt: time.Now(), + }) tx.Commit() resp.SUCCESS(c) diff --git a/api/handler/sd_handler.go b/api/handler/sd_handler.go index de3665fa..f7411b6c 100644 --- a/api/handler/sd_handler.go +++ b/api/handler/sd_handler.go @@ -72,8 +72,8 @@ func (h *SdJobHandler) checkLimits(c *gin.Context) bool { return false } - if user.ImgCalls <= 0 { - resp.ERROR(c, "您的绘图次数不足,请联系管理员充值!") + if user.Power < h.App.SysConfig.SdPower { + resp.ERROR(c, "当前用户剩余算力不足以完成本次绘画!") return false } @@ -140,6 +140,7 @@ func (h *SdJobHandler) Image(c *gin.Context) { Params: utils.JsonEncode(params), Prompt: data.Prompt, Progress: 0, + Power: h.App.SysConfig.SdPower, CreatedAt: time.Now(), } res := h.db.Create(&job) @@ -162,8 +163,23 @@ func (h *SdJobHandler) Image(c *gin.Context) { _ = client.Send([]byte("Task Updated")) } - // update user's img calls - h.db.Model(&model.User{}).Where("id = ?", job.UserId).UpdateColumn("img_calls", gorm.Expr("img_calls - ?", 1)) + // update user's power + tx := h.db.Model(&model.User{}).Where("id = ?", job.UserId).UpdateColumn("power", gorm.Expr("power - ?", job.Power)) + // 记录算力变化日志 + if tx.Error == nil && tx.RowsAffected > 0 { + user, _ := utils.GetLoginUser(c, h.db) + h.db.Create(&model.PowerLog{ + UserId: user.Id, + Username: user.Username, + Type: types.PowerConsume, + Amount: job.Power, + Balance: user.Power - job.Power, + Mark: types.PowerSub, + Model: "stable-diffusion", + Remark: fmt.Sprintf("绘图操作,任务ID:%s", job.TaskId), + CreatedAt: time.Now(), + }) + } resp.SUCCESS(c) } @@ -232,18 +248,7 @@ func (h *SdJobHandler) getData(finish bool, userId uint, page int, pageSize int, continue } - if job.Progress == -1 { - h.db.Delete(&model.SdJob{Id: job.Id}) - } - if item.Progress < 100 { - // 5 分钟还没完成的任务直接删除 - if time.Now().Sub(item.CreatedAt) > time.Minute*5 { - h.db.Delete(&item) - // 退回绘图次数 - h.db.Model(&model.User{}).Where("id = ?", item.UserId).UpdateColumn("img_calls", gorm.Expr("img_calls + ?", 1)) - continue - } // 正在运行中任务使用代理访问图片 image, err := utils.DownloadImage(item.ImgURL, "") if err == nil { diff --git a/api/handler/user_handler.go b/api/handler/user_handler.go index 1b63b69d..b9f32d94 100644 --- a/api/handler/user_handler.go +++ b/api/handler/user_handler.go @@ -102,8 +102,7 @@ func (h *UserHandler) Register(c *gin.Context) { types.ChatGLM: "", }, }), - Calls: h.App.SysConfig.InitChatCalls, - ImgCalls: h.App.SysConfig.InitImgCalls, + Power: h.App.SysConfig.InitPower, } res = h.db.Create(&user) @@ -117,11 +116,8 @@ func (h *UserHandler) Register(c *gin.Context) { if data.InviteCode != "" { // 增加邀请数量 h.db.Model(&model.InviteCode{}).Where("code = ?", data.InviteCode).UpdateColumn("reg_num", gorm.Expr("reg_num + ?", 1)) - if h.App.SysConfig.InviteChatCalls > 0 { - h.db.Model(&model.User{}).Where("id = ?", inviteCode.UserId).UpdateColumn("calls", gorm.Expr("calls + ?", h.App.SysConfig.InviteChatCalls)) - } - if h.App.SysConfig.InviteImgCalls > 0 { - h.db.Model(&model.User{}).Where("id = ?", inviteCode.UserId).UpdateColumn("img_calls", gorm.Expr("img_calls + ?", h.App.SysConfig.InviteImgCalls)) + if h.App.SysConfig.InvitePower > 0 { + h.db.Model(&model.User{}).Where("id = ?", inviteCode.UserId).UpdateColumn("power", gorm.Expr("power + ?", h.App.SysConfig.InvitePower)) } // 添加邀请记录 @@ -130,7 +126,7 @@ func (h *UserHandler) Register(c *gin.Context) { UserId: user.Id, Username: user.Username, InviteCode: inviteCode.Code, - Reward: utils.JsonEncode(types.InviteReward{ChatCalls: h.App.SysConfig.InviteChatCalls, ImgCalls: h.App.SysConfig.InviteImgCalls}), + Reward: utils.JsonEncode(types.InviteReward{Power: h.App.SysConfig.InvitePower}), }) } @@ -254,10 +250,7 @@ type userProfile struct { Username string `json:"username"` Avatar string `json:"avatar"` ChatConfig types.UserChatConfig `json:"chat_config"` - Calls int `json:"calls"` - ImgCalls int `json:"img_calls"` - TotalTokens int64 `json:"total_tokens"` - Tokens int `json:"tokens"` + Power int `json:"power"` ExpiredTime int64 `json:"expired_time"` Vip bool `json:"vip"` } diff --git a/api/service/mj/plus/service.go b/api/service/mj/plus/service.go index 309a3158..f03fd679 100644 --- a/api/service/mj/plus/service.go +++ b/api/service/mj/plus/service.go @@ -96,12 +96,6 @@ func (s *Service) Run() { s.db.Updates(&job) // 任务失败,通知前端 s.notifyQueue.RPush(task.UserId) - // restore img_call quota - if task.Type.String() != types.TaskUpscale.String() { - s.db.Model(&model.User{}).Where("id = ?", task.UserId).UpdateColumn("img_calls", gorm.Expr("img_calls + ?", 1)) - } - - // TODO: 任务提交失败,加入队列重试 continue } logger.Infof("任务提交成功:%+v", res) diff --git a/api/service/mj/pool.go b/api/service/mj/pool.go index 4db4b510..fdf3428a 100644 --- a/api/service/mj/pool.go +++ b/api/service/mj/pool.go @@ -191,28 +191,43 @@ func (p *ServicePool) SyncTaskProgress() { go func() { var items []model.MidJourneyJob for { - res := p.db.Where("progress >= ? AND progress < ?", 0, 100).Find(&items) + res := p.db.Where("progress < ?", 100).Find(&items) if res.Error != nil { continue } - for _, v := range items { - // 30 分钟还没完成的任务直接删除 - if time.Now().Sub(v.CreatedAt) > time.Minute*30 { - p.db.Delete(&v) - // 非放大任务,退回绘图次数 - if v.Type != types.TaskUpscale.String() { - p.db.Model(&model.User{}).Where("id = ?", v.UserId).UpdateColumn("img_calls", gorm.Expr("img_calls + ?", 1)) + for _, job := range items { + // 失败或者 30 分钟还没完成的任务删除并退回算力 + if time.Now().Sub(job.CreatedAt) > time.Minute*30 || job.Progress == -1 { + p.db.Delete(&job) + // 略过 Upscale 任务 + if job.Type != types.TaskUpscale.String() { + continue } + tx := p.db.Model(&model.User{}).Where("id = ?", job.UserId).UpdateColumn("power", gorm.Expr("power + ?", job.Power)) + if tx.Error == nil && tx.RowsAffected > 0 { + var user model.User + p.db.Where("id = ?", job.UserId).First(&user) + p.db.Create(&model.PowerLog{ + UserId: user.Id, + Username: user.Username, + Type: types.PowerConsume, + Amount: job.Power, + Balance: user.Power + job.Power, + Mark: types.PowerAdd, + Model: "mid-journey", + Remark: fmt.Sprintf("绘画任务失败,退回算力。任务ID:%s", job.TaskId), + CreatedAt: time.Now(), + }) + } + } + + if !strings.HasPrefix(job.ChannelId, "mj-service-plus") { continue } - if !strings.HasPrefix(v.ChannelId, "mj-service-plus") { - continue - } - - if servicePlus := p.getServicePlus(v.ChannelId); servicePlus != nil { - _ = servicePlus.Notify(v) + if servicePlus := p.getServicePlus(job.ChannelId); servicePlus != nil { + _ = servicePlus.Notify(job) } } diff --git a/api/service/mj/service.go b/api/service/mj/service.go index b9f028f3..ab0abbf0 100644 --- a/api/service/mj/service.go +++ b/api/service/mj/service.go @@ -84,7 +84,7 @@ func (s *Service) Run() { if err != nil { logger.Error("绘画任务执行失败:", err.Error()) // update the task progress - s.db.Model(&model.MidJourneyJob{Id: uint(task.Id)}).UpdateColumns(map[string]interface{}{ + s.db.Model(&model.MidJourneyJob{Id: task.Id}).UpdateColumns(map[string]interface{}{ "progress": -1, "err_msg": err.Error(), }) diff --git a/api/service/sd/pool.go b/api/service/sd/pool.go index db7d09c7..e52baed9 100644 --- a/api/service/sd/pool.go +++ b/api/service/sd/pool.go @@ -4,7 +4,9 @@ import ( "chatplus/core/types" "chatplus/service/oss" "chatplus/store" + "chatplus/store/model" "fmt" + "time" "github.com/go-redis/redis/v8" "gorm.io/gorm" @@ -14,6 +16,7 @@ type ServicePool struct { services []*Service taskQueue *store.RedisQueue notifyQueue *store.RedisQueue + db *gorm.DB Clients *types.LMap[uint, *types.WsClient] // UserId => Client } @@ -42,6 +45,7 @@ func NewServicePool(db *gorm.DB, redisCli *redis.Client, manager *oss.UploaderMa taskQueue: taskQueue, notifyQueue: notifyQueue, services: services, + db: db, Clients: types.NewLMap[uint, *types.WsClient](), } } @@ -72,6 +76,46 @@ func (p *ServicePool) CheckTaskNotify() { }() } +// CheckTaskStatus 检查任务状态,自动删除过期或者失败的任务 +func (p *ServicePool) CheckTaskStatus() { + go func() { + for { + var jobs []model.SdJob + res := p.db.Where("progress < ?", 100).Find(&jobs) + if res.Error != nil { + time.Sleep(5 * time.Second) + continue + } + + for _, job := range jobs { + // 5 分钟还没完成的任务直接删除 + if time.Now().Sub(job.CreatedAt) > time.Minute*5 || job.Progress == -1 { + p.db.Delete(&job) + var user model.User + p.db.Where("id = ?", job.UserId).First(&user) + // 退回绘图次数 + res = p.db.Model(&model.User{}).Where("id = ?", job.UserId).UpdateColumn("power", gorm.Expr("power + ?", job.Power)) + if res.Error == nil && res.RowsAffected > 0 { + p.db.Create(&model.PowerLog{ + UserId: user.Id, + Username: user.Username, + Type: types.PowerConsume, + Amount: job.Power, + Balance: user.Power + job.Power, + Mark: types.PowerAdd, + Model: "stable-diffusion", + Remark: fmt.Sprintf("任务失败,退回算力。任务ID:%s", job.TaskId), + CreatedAt: time.Now(), + }) + } + continue + } + } + + } + }() +} + // HasAvailableService check if it has available mj service in pool func (p *ServicePool) HasAvailableService() bool { return len(p.services) > 0 diff --git a/api/service/sd/service.go b/api/service/sd/service.go index 4bb9aa71..074c34c5 100644 --- a/api/service/sd/service.go +++ b/api/service/sd/service.go @@ -74,8 +74,6 @@ func (s *Service) Run() { "progress": -1, "err_msg": err.Error(), }) - // restore img_call quota - s.db.Model(&model.User{}).Where("id = ?", task.UserId).UpdateColumn("img_calls", gorm.Expr("img_calls + ?", 1)) // release task num atomic.AddInt32(&s.handledTaskNum, -1) // 通知前端,任务失败 @@ -307,8 +305,6 @@ func (s *Service) callback(data CBReq) { "progress": -1, "err_msg": data.Message, }) - // restore img_calls - s.db.Model(&model.User{}).Where("id = ? AND img_calls > 0", data.UserId).UpdateColumn("img_calls", gorm.Expr("img_calls + ?", 1)) } // 发送更新状态信号 diff --git a/api/service/xxl_job_service.go b/api/service/xxl_job_service.go index 222cbefa..977e34ac 100644 --- a/api/service/xxl_job_service.go +++ b/api/service/xxl_job_service.go @@ -39,7 +39,7 @@ func NewXXLJobExecutor(config *types.AppConfig, db *gorm.DB) *XXLJobExecutor { func (e *XXLJobExecutor) Run() error { e.executor.RegTask("ClearOrders", e.ClearOrders) - e.executor.RegTask("ResetVipCalls", e.ResetVipCalls) + e.executor.RegTask("ResetVipPower", e.ResetVipPower) return e.executor.Run() } @@ -68,8 +68,8 @@ func (e *XXLJobExecutor) ClearOrders(cxt context.Context, param *xxl.RunReq) (ms return fmt.Sprintf("Clear order successfully, affect rows: %d", res.RowsAffected) } -// ResetVipCalls 清理过期的 VIP 会员 -func (e *XXLJobExecutor) ResetVipCalls(cxt context.Context, param *xxl.RunReq) (msg string) { +// ResetVipPower 清理过期的 VIP 会员 +func (e *XXLJobExecutor) ResetVipPower(cxt context.Context, param *xxl.RunReq) (msg string) { logger.Info("开始进行月底账号盘点...") var users []model.User res := e.db.Where("vip = ?", 1).Find(&users) @@ -89,55 +89,27 @@ func (e *XXLJobExecutor) ResetVipCalls(cxt context.Context, param *xxl.RunReq) ( return "error with decode system config: " + err.Error() } - // 获取本月月初时间 - currentTime := time.Now() - year, month, _ := currentTime.Date() - firstOfMonth := time.Date(year, month, 1, 0, 0, 0, 0, currentTime.Location()).Unix() for _, u := range users { - // 账号到期,直接清零 - if u.ExpiredTime <= currentTime.Unix() { - logger.Info("账号过期:", u.Username) - u.Calls = 0 - u.Vip = false - } else { - if u.Calls <= 0 { - u.Calls = 0 - } - if u.ImgCalls <= 0 { - u.ImgCalls = 0 - } - // 如果该用户当月有充值点卡,则将点卡中未用完的点数结余到下个月 - var orders []model.Order - e.db.Debug().Where("user_id = ? AND pay_time > ?", u.Id, firstOfMonth).Find(&orders) - var calls = 0 - var imgCalls = 0 - for _, o := range orders { - var remark types.OrderRemark - err = utils.JsonDecode(o.Remark, &remark) - if err != nil { - continue - } - if remark.Days > 0 { // 会员续费 - continue - } - calls += remark.Calls - imgCalls += remark.ImgCalls - } - if u.Calls > calls { // 本月套餐没有用完 - u.Calls = calls + config.VipMonthCalls - } else { - u.Calls = u.Calls + config.VipMonthCalls - } - if u.ImgCalls > imgCalls { // 本月套餐没有用完 - u.ImgCalls = imgCalls + config.VipMonthImgCalls - } else { - u.ImgCalls = u.ImgCalls + config.VipMonthImgCalls - } - logger.Infof("%s 点卡结余:%d", u.Username, calls) + if u.Power <= 0 { + u.Power = 0 } - u.Tokens = 0 + u.Power += config.VipMonthPower // update user - e.db.Updates(&u) + tx := e.db.Updates(&u) + // 记录算力充值日志 + if tx.Error == nil { + e.db.Create(&model.PowerLog{ + UserId: u.Id, + Username: u.Username, + Type: types.PowerRecharge, + Amount: config.VipMonthPower, + Mark: types.PowerAdd, + Balance: u.Power + config.VipMonthPower, + Model: "", + Remark: fmt.Sprintf("月底盘点,会员每月赠送算力:%d", config.VipMonthPower), + CreatedAt: time.Now(), + }) + } } logger.Info("月底盘点完成!") return "success" diff --git a/api/store/model/chat_model.go b/api/store/model/chat_model.go index b223d34a..71a2ab69 100644 --- a/api/store/model/chat_model.go +++ b/api/store/model/chat_model.go @@ -7,6 +7,6 @@ type ChatModel struct { Value string // API Key 的值 SortNum int Enabled bool - Weight int // 对话权重,每次对话扣减多少次对话额度 + Power int // 每次对话消耗算力 Open bool // 是否开放模型给所有人使用 } diff --git a/api/store/model/mj_job.go b/api/store/model/mj_job.go index 1b1494a9..b4e03a6c 100644 --- a/api/store/model/mj_job.go +++ b/api/store/model/mj_job.go @@ -18,6 +18,7 @@ type MidJourneyJob struct { UseProxy bool // 是否使用反代加载图片 Publish bool //是否发布图片到画廊 ErrMsg string // 报错信息 + Power int // 消耗算力 CreatedAt time.Time } diff --git a/api/store/model/power_log.go b/api/store/model/power_log.go new file mode 100644 index 00000000..e0d6a527 --- /dev/null +++ b/api/store/model/power_log.go @@ -0,0 +1,20 @@ +package model + +import ( + "chatplus/core/types" + "time" +) + +// PowerLog 算力消费日志 +type PowerLog struct { + Id uint `gorm:"primarykey;column:id"` + UserId uint + Username string + Type types.PowerType + Amount int + Balance int + Model string // 模型 + Remark string // 备注 + Mark types.PowerMark // 资金类型 + CreatedAt time.Time +} diff --git a/api/store/model/product.go b/api/store/model/product.go index b89c4db6..66e35d14 100644 --- a/api/store/model/product.go +++ b/api/store/model/product.go @@ -7,8 +7,7 @@ type Product struct { Price float64 Discount float64 Days int - Calls int - ImgCalls int + Power int Enabled bool Sales int SortNum int diff --git a/api/store/model/sd_job.go b/api/store/model/sd_job.go index ce860753..8542c307 100644 --- a/api/store/model/sd_job.go +++ b/api/store/model/sd_job.go @@ -13,6 +13,7 @@ type SdJob struct { Params string Publish bool //是否发布图片到画廊 ErrMsg string // 报错信息 + Power int // 消耗算力 CreatedAt time.Time } diff --git a/api/store/model/user.go b/api/store/model/user.go index fb2dfcc2..41d09905 100644 --- a/api/store/model/user.go +++ b/api/store/model/user.go @@ -7,9 +7,7 @@ type User struct { Password string Avatar string Salt string // 密码盐 - TotalTokens int64 // 总消耗 tokens - Calls int // 剩余对话次数 - ImgCalls int // 剩余绘图次数 + Power int // 剩余算力 ChatConfig string `gorm:"column:chat_config_json"` // 聊天配置 json ChatRoles string `gorm:"column:chat_roles_json"` // 聊天角色 ChatModels string `gorm:"column:chat_models_json"` // AI 模型,不同的用户拥有不同的聊天模型 @@ -18,5 +16,4 @@ type User struct { LastLoginAt int64 // 最后登录时间 LastLoginIp string // 最后登录 IP Vip bool // 是否 VIP 会员 - Tokens int } diff --git a/api/store/vo/mj_job.go b/api/store/vo/mj_job.go index 165af827..59ec11c6 100644 --- a/api/store/vo/mj_job.go +++ b/api/store/vo/mj_job.go @@ -18,5 +18,6 @@ type MidJourneyJob struct { UseProxy bool `json:"use_proxy"` Publish bool `json:"publish"` ErrMsg string `json:"err_msg"` + Power int `json:"power"` CreatedAt time.Time `json:"created_at"` } diff --git a/api/store/vo/power_log.go b/api/store/vo/power_log.go new file mode 100644 index 00000000..ebe48c65 --- /dev/null +++ b/api/store/vo/power_log.go @@ -0,0 +1,16 @@ +package vo + +import "chatplus/core/types" + +type PowerLog struct { + Id uint `json:"id"` + UserId uint `json:"user_id"` + Username string `json:"username"` + Type types.PowerType `json:"name"` + Amount int `json:"amount"` + Mark types.PowerMark `json:"fund_type"` + Balance int `json:"balance"` + Model string `json:"model"` + Remark string `json:"remark"` + CreatedAt int64 `json:"created_at"` +} diff --git a/api/store/vo/product.go b/api/store/vo/product.go index 83b40df1..0cc5e198 100644 --- a/api/store/vo/product.go +++ b/api/store/vo/product.go @@ -6,8 +6,7 @@ type Product struct { Price float64 `json:"price"` Discount float64 `json:"discount"` Days int `json:"days"` - Calls int `json:"calls"` - ImgCalls int `json:"img_calls"` + Power int `json:"power"` Enabled bool `json:"enabled"` Sales int `json:"sales"` SortNum int `json:"sort_num"` diff --git a/api/store/vo/reward.go b/api/store/vo/reward.go index cb6490e1..ab443ec8 100644 --- a/api/store/vo/reward.go +++ b/api/store/vo/reward.go @@ -12,6 +12,5 @@ type Reward struct { } type RewardExchange struct { - Calls int `json:"calls"` - ImgCalls int `json:"img_calls"` + Power int `json:"calls"` } diff --git a/api/store/vo/sd_job.go b/api/store/vo/sd_job.go index 57df9739..0fcf95ab 100644 --- a/api/store/vo/sd_job.go +++ b/api/store/vo/sd_job.go @@ -16,5 +16,6 @@ type SdJob struct { Prompt string `json:"prompt"` Publish bool `json:"publish"` ErrMsg string `json:"err_msg"` + Power int `json:"power"` CreatedAt time.Time `json:"created_at"` } diff --git a/api/store/vo/user.go b/api/store/vo/user.go index b8807d25..6682b3b7 100644 --- a/api/store/vo/user.go +++ b/api/store/vo/user.go @@ -7,10 +7,8 @@ type User struct { Username string `json:"username"` Nickname string `json:"nickname"` Avatar string `json:"avatar"` - Salt string `json:"salt"` // 密码盐 - TotalTokens int64 `json:"total_tokens"` // 总消耗tokens - Calls int `json:"calls"` // 剩余对话次数 - ImgCalls int `json:"img_calls"` + Salt string `json:"salt"` // 密码盐 + Power int `json:"calls"` // 剩余算力 ChatConfig types.UserChatConfig `json:"chat_config"` // 聊天配置 ChatRoles []string `json:"chat_roles"` // 聊天角色集合 ChatModels []string `json:"chat_models"` // AI模型集合 @@ -19,5 +17,4 @@ type User struct { LastLoginAt int64 `json:"last_login_at"` // 最后登录时间 LastLoginIp string `json:"last_login_ip"` // 最后登录 IP Vip bool `json:"vip"` - Tokens int `json:"token"` // 当月消耗的 fee } diff --git a/database/update-v3.2.8.sql b/database/update-v3.2.8.sql deleted file mode 100644 index bd031e71..00000000 --- a/database/update-v3.2.8.sql +++ /dev/null @@ -1,23 +0,0 @@ --- 删除用户名重复的用户,只保留一条 -DELETE FROM chatgpt_users -WHERE username IN ( - SELECT username - FROM ( - SELECT username - FROM chatgpt_users - GROUP BY username - HAVING COUNT(*) > 1 - ) AS temp -) AND id NOT IN ( - SELECT MIN(id) - FROM ( - SELECT id, username - FROM chatgpt_users - GROUP BY id, username - HAVING COUNT(*) > 1 - ) AS temp - GROUP BY username -); - --- 给 username 字段建立唯一索引 -ALTER TABLE `chatgpt_users` ADD UNIQUE(`username`) \ No newline at end of file diff --git a/database/update-v4.0.0.sql b/database/update-v4.0.0.sql new file mode 100644 index 00000000..d589a239 --- /dev/null +++ b/database/update-v4.0.0.sql @@ -0,0 +1,44 @@ +-- 删除用户名重复的用户,只保留一条 +DELETE FROM chatgpt_users +WHERE username IN ( + SELECT username + FROM ( + SELECT username + FROM chatgpt_users + GROUP BY username + HAVING COUNT(*) > 1 + ) AS temp +) AND id NOT IN ( + SELECT MIN(id) + FROM ( + SELECT id, username + FROM chatgpt_users + GROUP BY id, username + HAVING COUNT(*) > 1 + ) AS temp + GROUP BY username +); + +-- 给 username 字段建立唯一索引 +ALTER TABLE `chatgpt_users` ADD UNIQUE(`username`) + +-- 当前用户剩余算力 +ALTER TABLE `chatgpt_users` CHANGE `calls` `power` INT NOT NULL DEFAULT '0' COMMENT '剩余算力'; +ALTER TABLE `chatgpt_users` +DROP `total_tokens`, + DROP `tokens`, + DROP `img_calls`; + +ALTER TABLE `chatgpt_chat_models` CHANGE `weight` `power` TINYINT NOT NULL COMMENT '消耗算力点数'; +ALTER TABLE `chatgpt_chat_models` ADD `temperature` FLOAT(3,2) NOT NULL DEFAULT '1' COMMENT '模型创意度' AFTER `power`, ADD `max_tokens` INT(11) NOT NULL DEFAULT '1024' COMMENT '最大响应长度' AFTER `temperature`, ADD `max_context` INT(11) NOT NULL DEFAULT '4096' COMMENT '最大上下文长度' AFTER `max_tokens`; + +CREATE TABLE `chatgpt_plus`.`chatgpt_power_logs` ( `id` INT(11) NOT NULL AUTO_INCREMENT , `user_id` INT(11) NOT NULL COMMENT '用户ID' , `username` VARCHAR(30) NOT NULL COMMENT '用户名' , `type` TINYINT(1) NOT NULL COMMENT '类型(1:充值,2:消费,3:退费)' , `amount` SMALLINT(3) NOT NULL COMMENT '算力花费' , `balance` INT(11) NOT NULL COMMENT '余额' , `model` VARCHAR(30) NOT NULL COMMENT '模型' , `remark` VARCHAR(255) NOT NULL COMMENT '备注' , `created_at` DATETIME NOT NULL COMMENT '创建时间' , PRIMARY KEY (`id`)) ENGINE = InnoDB COMMENT = '用户算力消费日志'; + +ALTER TABLE `chatgpt_products` CHANGE `calls` `power` INT(11) NOT NULL DEFAULT '0' COMMENT '增加算力值'; + +ALTER TABLE `chatgpt_products` DROP `img_calls`; + +ALTER TABLE `chatgpt_power_logs` CHANGE `amount` `amount` SMALLINT NOT NULL COMMENT '算力数值'; +ALTER TABLE `chatgpt_power_logs` ADD `mark` TINYINT(1) NOT NULL COMMENT '资金类型(0:支出,1:收入)' AFTER `remark`; +ALTER TABLE `chatgpt_mj_jobs` ADD `power` SMALLINT(5) NOT NULL DEFAULT '0' COMMENT '消耗算力' AFTER `err_msg`; +ALTER TABLE `chatgpt_sd_jobs` ADD `power` SMALLINT(5) NOT NULL DEFAULT '0' COMMENT '消耗算力' AFTER `err_msg`; \ No newline at end of file diff --git a/gpt-vue/.gitignore b/gpt-vue/.gitignore new file mode 100644 index 00000000..f06235c4 --- /dev/null +++ b/gpt-vue/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist diff --git a/web/src/components/ChatPrompt.vue b/web/src/components/ChatPrompt.vue index 9bf53c43..57f136c5 100644 --- a/web/src/components/ChatPrompt.vue +++ b/web/src/components/ChatPrompt.vue @@ -9,7 +9,7 @@
{{ createdAt }} - 算力消耗: {{ finalTokens }} + Tokens: {{ finalTokens }}
diff --git a/web/src/components/ChatReply.vue b/web/src/components/ChatReply.vue index 752282dc..a15d612b 100644 --- a/web/src/components/ChatReply.vue +++ b/web/src/components/ChatReply.vue @@ -9,12 +9,12 @@
{{ createdAt }} - 算力消耗: {{ tokens }} + Tokens: {{ tokens }} @@ -24,7 +24,6 @@
- diff --git a/web/src/views/ImageMj.vue b/web/src/views/ImageMj.vue index 5ae1289b..6ecae7a5 100644 --- a/web/src/views/ImageMj.vue +++ b/web/src/views/ImageMj.vue @@ -438,7 +438,7 @@