fix conflicts

This commit is contained in:
RockYang
2024-03-12 18:03:24 +08:00
239 changed files with 602 additions and 452 deletions
+3
View File
@@ -2,6 +2,9 @@
## v3.2.8 ## v3.2.8
* 功能优化:SD 绘画页面采用 websocket 替换 http 轮询机制,节省带宽 * 功能优化:SD 绘画页面采用 websocket 替换 http 轮询机制,节省带宽
* 功能优化:移动端聊天页面图片支持预览和放大功能 * 功能优化:移动端聊天页面图片支持预览和放大功能
* 功能优化:MJ 和 SD 页面数据分页加载,解决一次性加载太多数据导致页面卡顿的问题
* 功能优化:手机端 MJ 增加提示词翻译功能
* 功能优化:控制台订单管理页面显示未支付订单,并提供订单删除功能。
* 功能新增:移动端支持充值 * 功能新增:移动端支持充值
## v3.2.7 ## v3.2.7
+2 -2
View File
@@ -28,7 +28,7 @@ type AppServer struct {
Debug bool Debug bool
Config *types.AppConfig Config *types.AppConfig
Engine *gin.Engine Engine *gin.Engine
ChatContexts *types.LMap[string, []interface{}] // 聊天上下文 Map [chatId] => []Message ChatContexts *types.LMap[string, []types.Message] // 聊天上下文 Map [chatId] => []Message
ChatConfig *types.ChatConfig // chat config cache ChatConfig *types.ChatConfig // chat config cache
SysConfig *types.SystemConfig // system config cache SysConfig *types.SystemConfig // system config cache
@@ -47,7 +47,7 @@ func NewServer(appConfig *types.AppConfig) *AppServer {
Debug: false, Debug: false,
Config: appConfig, Config: appConfig,
Engine: gin.Default(), Engine: gin.Default(),
ChatContexts: types.NewLMap[string, []interface{}](), ChatContexts: types.NewLMap[string, []types.Message](),
ChatSession: types.NewLMap[string, *types.ChatSession](), ChatSession: types.NewLMap[string, *types.ChatSession](),
ChatClients: types.NewLMap[string, *types.WsClient](), ChatClients: types.NewLMap[string, *types.WsClient](),
ReqCancelFunc: types.NewLMap[string, context.CancelFunc](), ReqCancelFunc: types.NewLMap[string, context.CancelFunc](),
+19 -1
View File
@@ -57,7 +57,7 @@ type ChatModel struct {
Id uint `json:"id"` Id uint `json:"id"`
Platform Platform `json:"platform"` Platform Platform `json:"platform"`
Value string `json:"value"` Value string `json:"value"`
Weight int `json:"weight"` Power int `json:"power"`
} }
type ApiError struct { type ApiError struct {
@@ -92,3 +92,21 @@ func GetModelMaxToken(model string) int {
} }
return 4096 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)
)
+12 -17
View File
@@ -158,37 +158,32 @@ type UserChatConfig struct {
ApiKeys map[Platform]string `json:"api_keys"` ApiKeys map[Platform]string `json:"api_keys"`
} }
type InviteReward struct {
ChatCalls int `json:"chat_calls"`
ImgCalls int `json:"img_calls"`
}
type ModelAPIConfig struct { type ModelAPIConfig struct {
Temperature float32 `json:"temperature"` Temperature float32 `json:"temperature"`
MaxTokens int `json:"max_tokens"` MaxTokens int `json:"max_tokens"`
} }
type SystemConfig struct { type SystemConfig struct {
Title string `json:"title"` Title string `json:"title"`
AdminTitle string `json:"admin_title"` AdminTitle string `json:"admin_title"`
InitChatCalls int `json:"init_chat_calls"` // 新用户注册赠送对话次数 InitPower int `json:"init_power"` // 新用户注册赠送算力值
InitImgCalls int `json:"init_img_calls"` // 新用户注册赠送绘图次数
VipMonthCalls int `json:"vip_month_calls"` // VIP 会员每月赠送的对话次数
VipMonthImgCalls int `json:"vip_month_img_calls"` // VIP 会员每月赠送绘图次数
RegisterWays []string `json:"register_ways"` // 注册方式:支持手机,邮箱注册 RegisterWays []string `json:"register_ways"` // 注册方式:支持手机,邮箱注册
EnabledRegister bool `json:"enabled_register"` // 是否开放注册 EnabledRegister bool `json:"enabled_register"` // 是否开放注册
RewardImg string `json:"reward_img"` // 众筹收款二维码地址 RewardImg string `json:"reward_img"` // 众筹收款二维码地址
EnabledReward bool `json:"enabled_reward"` // 启用众筹功能 EnabledReward bool `json:"enabled_reward"` // 启用众筹功能
ChatCallPrice float64 `json:"chat_call_price"` // 对话单次调用费用 PowerPrice float64 `json:"power_price"` // 算力单价
ImgCallPrice float64 `json:"img_call_price"` // 绘图单次调用费用
OrderPayTimeout int `json:"order_pay_timeout"` //订单支付超时时间 OrderPayTimeout int `json:"order_pay_timeout"` //订单支付超时时间
DefaultModels []string `json:"default_models"` // 默认开通的 AI 模型 DefaultModels []string `json:"default_models"` // 默认开通的 AI 模型
OrderPayInfoText string `json:"order_pay_info_text"` // 订单支付页面说明文字 OrderPayInfoText string `json:"order_pay_info_text"` // 订单支付页面说明文字
InviteChatCalls int `json:"invite_chat_calls"` // 邀请用户注册奖励对话次数 InvitePower int `json:"invite_power"` // 邀请用户赠送算力值
InviteImgCalls int `json:"invite_img_calls"` // 邀请用户注册奖励绘图次数 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"` // 微信客服地址 WechatCardURL string `json:"wechat_card_url"` // 微信客服地址
} }
+1 -1
View File
@@ -9,7 +9,7 @@ type MKey interface {
string | int | uint string | int | uint
} }
type MValue interface { type MValue interface {
*WsClient | *ChatSession | context.CancelFunc | []interface{} *WsClient | *ChatSession | context.CancelFunc | []Message
} }
type LMap[K MKey, T MValue] struct { type LMap[K MKey, T MValue] struct {
lock sync.RWMutex lock sync.RWMutex
+3 -4
View File
@@ -9,10 +9,9 @@ const (
) )
type OrderRemark struct { type OrderRemark struct {
Days int `json:"days"` // 有效期 Days int `json:"days"` // 有效期
Calls int `json:"calls"` // 增加对话次 Power int `json:"power"` // 增加算力点
ImgCalls int `json:"img_calls"` // 增加绘图次数 Name string `json:"name"` // 产品名称
Name string `json:"name"` // 产品名称
Price float64 `json:"price"` Price float64 `json:"price"`
Discount float64 `json:"discount"` Discount float64 `json:"discount"`
} }
+1 -1
View File
@@ -48,7 +48,7 @@ func (h *ChatModelHandler) Save(c *gin.Context) {
Enabled: data.Enabled, Enabled: data.Enabled,
SortNum: data.SortNum, SortNum: data.SortNum,
Open: data.Open, Open: data.Open,
Weight: data.Weight} Power: data.Weight}
item.Id = data.Id item.Id = data.Id
if item.Id > 0 { if item.Id > 0 {
item.CreatedAt = time.Unix(data.CreatedAt, 0) item.CreatedAt = time.Unix(data.CreatedAt, 0)
+2 -4
View File
@@ -32,8 +32,7 @@ func (h *ProductHandler) Save(c *gin.Context) {
Discount float64 `json:"discount"` Discount float64 `json:"discount"`
Enabled bool `json:"enabled"` Enabled bool `json:"enabled"`
Days int `json:"days"` Days int `json:"days"`
Calls int `json:"calls"` Power int `json:"power"`
ImgCalls int `json:"img_calls"`
CreatedAt int64 `json:"created_at"` CreatedAt int64 `json:"created_at"`
} }
if err := c.ShouldBindJSON(&data); err != nil { if err := c.ShouldBindJSON(&data); err != nil {
@@ -46,8 +45,7 @@ func (h *ProductHandler) Save(c *gin.Context) {
Price: data.Price, Price: data.Price,
Discount: data.Discount, Discount: data.Discount,
Days: data.Days, Days: data.Days,
Calls: data.Calls, Power: data.Power,
ImgCalls: data.ImgCalls,
Enabled: data.Enabled} Enabled: data.Enabled}
item.Id = data.Id item.Id = data.Id
if item.Id > 0 { if item.Id > 0 {
-6
View File
@@ -66,8 +66,6 @@ func (h *UserHandler) Save(c *gin.Context) {
Id uint `json:"id"` Id uint `json:"id"`
Password string `json:"password"` Password string `json:"password"`
Username string `json:"username"` Username string `json:"username"`
Calls int `json:"calls"`
ImgCalls int `json:"img_calls"`
ChatRoles []string `json:"chat_roles"` ChatRoles []string `json:"chat_roles"`
ChatModels []string `json:"chat_models"` ChatModels []string `json:"chat_models"`
ExpiredTime string `json:"expired_time"` ExpiredTime string `json:"expired_time"`
@@ -86,8 +84,6 @@ func (h *UserHandler) Save(c *gin.Context) {
// 此处需要用 map 更新,用结构体无法更新 0 值 // 此处需要用 map 更新,用结构体无法更新 0 值
res = h.db.Model(&user).Updates(map[string]interface{}{ res = h.db.Model(&user).Updates(map[string]interface{}{
"username": data.Username, "username": data.Username,
"calls": data.Calls,
"img_calls": data.ImgCalls,
"status": data.Status, "status": data.Status,
"vip": data.Vip, "vip": data.Vip,
"chat_roles_json": utils.JsonEncode(data.ChatRoles), "chat_roles_json": utils.JsonEncode(data.ChatRoles),
@@ -113,8 +109,6 @@ func (h *UserHandler) Save(c *gin.Context) {
types.ChatGLM: "", types.ChatGLM: "",
}, },
}), }),
Calls: data.Calls,
ImgCalls: data.ImgCalls,
} }
res = h.db.Create(&u) res = h.db.Create(&u)
_ = utils.CopyObject(u, &userVo) _ = utils.CopyObject(u, &userVo)
+6 -8
View File
@@ -19,7 +19,7 @@ import (
// 微软 Azure 模型消息发送实现 // 微软 Azure 模型消息发送实现
func (h *ChatHandler) sendAzureMessage( func (h *ChatHandler) sendAzureMessage(
chatCtx []interface{}, chatCtx []types.Message,
req types.ApiRequest, req types.ApiRequest,
userVo vo.User, userVo vo.User,
ctx context.Context, ctx context.Context,
@@ -103,8 +103,6 @@ func (h *ChatHandler) sendAzureMessage(
// 消息发送成功 // 消息发送成功
if len(contents) > 0 { if len(contents) > 0 {
// 更新用户的对话次数
h.subUserCalls(userVo, session)
if message.Role == "" { if message.Role == "" {
message.Role = "assistant" message.Role = "assistant"
@@ -145,8 +143,8 @@ func (h *ChatHandler) sendAzureMessage(
} }
// 计算本次对话消耗的总 token 数量 // 计算本次对话消耗的总 token 数量
totalTokens, _ := utils.CalcTokens(message.Content, req.Model) replyTokens, _ := utils.CalcTokens(message.Content, req.Model)
totalTokens += getTotalTokens(req) replyTokens += getTotalTokens(req)
historyReplyMsg := model.ChatMessage{ historyReplyMsg := model.ChatMessage{
UserId: userVo.Id, UserId: userVo.Id,
@@ -155,7 +153,7 @@ func (h *ChatHandler) sendAzureMessage(
Type: types.ReplyMsg, Type: types.ReplyMsg,
Icon: role.Icon, Icon: role.Icon,
Content: message.Content, Content: message.Content,
Tokens: totalTokens, Tokens: replyTokens,
UseContext: true, UseContext: true,
Model: req.Model, Model: req.Model,
} }
@@ -166,8 +164,8 @@ func (h *ChatHandler) sendAzureMessage(
logger.Error("failed to save reply history message: ", res.Error) logger.Error("failed to save reply history message: ", res.Error)
} }
// 更新用户信息 // 更新用户算力
h.incUserTokenFee(userVo.Id, totalTokens) h.subUserPower(userVo, session, promptToken, replyTokens)
} }
// 保存当前会话 // 保存当前会话
+5 -8
View File
@@ -36,7 +36,7 @@ type baiduResp struct {
// 百度文心一言消息发送实现 // 百度文心一言消息发送实现
func (h *ChatHandler) sendBaiduMessage( func (h *ChatHandler) sendBaiduMessage(
chatCtx []interface{}, chatCtx []types.Message,
req types.ApiRequest, req types.ApiRequest,
userVo vo.User, userVo vo.User,
ctx context.Context, ctx context.Context,
@@ -128,9 +128,6 @@ func (h *ChatHandler) sendBaiduMessage(
// 消息发送成功 // 消息发送成功
if len(contents) > 0 { if len(contents) > 0 {
// 更新用户的对话次数
h.subUserCalls(userVo, session)
if message.Role == "" { if message.Role == "" {
message.Role = "assistant" message.Role = "assistant"
} }
@@ -171,8 +168,8 @@ func (h *ChatHandler) sendBaiduMessage(
// for reply // for reply
// 计算本次对话消耗的总 token 数量 // 计算本次对话消耗的总 token 数量
replyToken, _ := utils.CalcTokens(message.Content, req.Model) replyTokens, _ := utils.CalcTokens(message.Content, req.Model)
totalTokens := replyToken + getTotalTokens(req) totalTokens := replyTokens + getTotalTokens(req)
historyReplyMsg := model.ChatMessage{ historyReplyMsg := model.ChatMessage{
UserId: userVo.Id, UserId: userVo.Id,
ChatId: session.ChatId, ChatId: session.ChatId,
@@ -190,8 +187,8 @@ func (h *ChatHandler) sendBaiduMessage(
if res.Error != nil { if res.Error != nil {
logger.Error("failed to save reply history message: ", res.Error) logger.Error("failed to save reply history message: ", res.Error)
} }
// 更新用户信息 // 更新用户算力
h.incUserTokenFee(userVo.Id, totalTokens) h.subUserPower(userVo, session, promptToken, replyTokens)
} }
// 保存当前会话 // 保存当前会话
+63 -58
View File
@@ -111,7 +111,7 @@ func (h *ChatHandler) ChatHandle(c *gin.Context) {
session.Model = types.ChatModel{ session.Model = types.ChatModel{
Id: chatModel.Id, Id: chatModel.Id,
Value: chatModel.Value, Value: chatModel.Value,
Weight: chatModel.Weight, Power: chatModel.Power,
Platform: types.Platform(chatModel.Platform)} Platform: types.Platform(chatModel.Platform)}
logger.Infof("New websocket connected, IP: %s, Username: %s", c.ClientIP(), session.Username) logger.Infof("New websocket connected, IP: %s, Username: %s", c.ClientIP(), session.Username)
var chatRole model.ChatRole var chatRole model.ChatRole
@@ -207,13 +207,13 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
return nil return nil
} }
if userVo.Calls < session.Model.Weight { if userVo.Power < session.Model.Power {
utils.ReplyMessage(ws, fmt.Sprintf("您当前剩余对话次数(%d)已不足以支付当前模型的单次对话需要消耗的对话额度(%d)!", userVo.Calls, session.Model.Weight)) utils.ReplyMessage(ws, fmt.Sprintf("您当前剩余对话次数(%d)已不足以支付当前模型的单次对话需要消耗的对话额度(%d)!", userVo.Power, session.Model.Power))
utils.ReplyMessage(ws, ErrImg) utils.ReplyMessage(ws, ErrImg)
return nil 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, "您的对话次数已经用尽,请联系管理员或者充值点卡继续对话!")
utils.ReplyMessage(ws, ErrImg) utils.ReplyMessage(ws, ErrImg)
return nil return nil
@@ -224,6 +224,14 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
utils.ReplyMessage(ws, ErrImg) utils.ReplyMessage(ws, ErrImg)
return nil return nil
} }
// 检查 prompt 长度是否超过了当前模型允许的最大上下文长度
promptTokens, err := utils.CalcTokens(prompt, session.Model.Value)
if promptTokens > types.GetModelMaxToken(session.Model.Value) {
utils.ReplyMessage(ws, "对话内容超出了当前模型允许的最大上下文长度!")
return nil
}
var req = types.ApiRequest{ var req = types.ApiRequest{
Model: session.Model.Value, Model: session.Model.Value,
Stream: true, Stream: true,
@@ -252,7 +260,6 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
} }
var tools = make([]interface{}, 0) var tools = make([]interface{}, 0)
var functions = make([]interface{}, 0)
for _, v := range items { for _, v := range items {
var parameters map[string]interface{} var parameters map[string]interface{}
err = utils.JsonDecode(v.Parameters, &parameters) err = utils.JsonDecode(v.Parameters, &parameters)
@@ -270,20 +277,11 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
"required": required, "required": required,
}, },
}) })
functions = append(functions, gin.H{
"name": v.Name,
"description": v.Description,
"parameters": parameters,
"required": required,
})
} }
//if len(tools) > 0 { if len(tools) > 0 {
// req.Tools = tools req.Tools = tools
// req.ToolChoice = "auto" req.ToolChoice = "auto"
//}
if len(functions) > 0 {
req.Functions = functions
} }
case types.XunFei: case types.XunFei:
@@ -301,40 +299,19 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
} }
// 加载聊天上下文 // 加载聊天上下文
var chatCtx []interface{} chatCtx := make([]types.Message, 0)
messages := make([]types.Message, 0)
if h.App.ChatConfig.EnableContext { if h.App.ChatConfig.EnableContext {
if h.App.ChatContexts.Has(session.ChatId) { if h.App.ChatContexts.Has(session.ChatId) {
chatCtx = h.App.ChatContexts.Get(session.ChatId) messages = h.App.ChatContexts.Get(session.ChatId)
} else { } else {
// calculate the tokens of current request, to prevent to exceeding the max tokens num _ = utils.JsonDecode(role.Context, &messages)
tokens := req.MaxTokens
tks, _ := utils.CalcTokens(utils.JsonEncode(req.Tools), req.Model)
tokens += tks
// loading the role context
var messages []types.Message
err := utils.JsonDecode(role.Context, &messages)
if err == nil {
for _, v := range messages {
tks, _ := utils.CalcTokens(v.Content, req.Model)
if tokens+tks >= types.GetModelMaxToken(req.Model) {
break
}
tokens += tks
chatCtx = append(chatCtx, v)
}
}
// loading recent chat history as chat context
if chatConfig.ContextDeep > 0 { if chatConfig.ContextDeep > 0 {
var historyMessages []model.ChatMessage var historyMessages []model.ChatMessage
res := h.db.Debug().Where("chat_id = ? and use_context = 1", session.ChatId).Limit(chatConfig.ContextDeep).Order("id desc").Find(&historyMessages) res := h.db.Where("chat_id = ? and use_context = 1", session.ChatId).Limit(chatConfig.ContextDeep).Order("id DESC").Find(&historyMessages)
if res.Error == nil { if res.Error == nil {
for i := len(historyMessages) - 1; i >= 0; i-- { for i := len(historyMessages) - 1; i >= 0; i-- {
msg := historyMessages[i] msg := historyMessages[i]
if tokens+msg.Tokens >= types.GetModelMaxToken(session.Model.Value) {
break
}
tokens += msg.Tokens
ms := types.Message{Role: "user", Content: msg.Content} ms := types.Message{Role: "user", Content: msg.Content}
if msg.Type == types.ReplyMsg { if msg.Type == types.ReplyMsg {
ms.Role = "assistant" ms.Role = "assistant"
@@ -344,6 +321,29 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
} }
} }
} }
// 计算当前请求的 token 总长度,确保不会超出最大上下文长度
// MaxContextLength = Response + Tool + Prompt + Context
tokens := req.MaxTokens // 最大响应长度
tks, _ := utils.CalcTokens(utils.JsonEncode(req.Tools), req.Model)
tokens += tks + promptTokens
for _, v := range messages {
tks, _ := utils.CalcTokens(v.Content, req.Model)
// 上下文 token 超出了模型的最大上下文长度
if tokens+tks >= types.GetModelMaxToken(req.Model) {
break
}
// 上下文的深度超出了模型的最大上下文深度
if len(chatCtx) >= h.App.ChatConfig.ContextDeep {
break
}
tokens += tks
chatCtx = append(chatCtx, v)
}
logger.Debugf("聊天上下文:%+v", chatCtx) logger.Debugf("聊天上下文:%+v", chatCtx)
} }
reqMgs := make([]interface{}, 0) reqMgs := make([]interface{}, 0)
@@ -533,23 +533,28 @@ func (h *ChatHandler) doRequest(ctx context.Context, req types.ApiRequest, platf
return client.Do(request) return client.Do(request)
} }
// 扣减用户的对话次数 // 扣减用户算力
func (h *ChatHandler) subUserCalls(userVo vo.User, session *types.ChatSession) { func (h *ChatHandler) subUserPower(userVo vo.User, session *types.ChatSession, promptTokens int, replyTokens int) {
// 仅当用户没有导入自己的 API KEY 时才进行扣减 power := 1
if userVo.ChatConfig.ApiKeys[session.Model.Platform] == "" { if session.Model.Power > 0 {
num := 1 power = session.Model.Power
if session.Model.Weight > 0 { }
num = session.Model.Weight res := h.db.Model(&model.User{}).Where("id = ?", userVo.Id).UpdateColumn("power", gorm.Expr("power - ?", power))
} if res.Error == nil {
h.db.Model(&model.User{}).Where("id = ?", userVo.Id).UpdateColumn("calls", gorm.Expr("calls - ?", num)) // 记录算力消费日志
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回复消息中生成的图片链接下载到本地 // 将AI回复消息中生成的图片链接下载到本地
+6 -8
View File
@@ -20,7 +20,7 @@ import (
// 清华大学 ChatGML 消息发送实现 // 清华大学 ChatGML 消息发送实现
func (h *ChatHandler) sendChatGLMMessage( func (h *ChatHandler) sendChatGLMMessage(
chatCtx []interface{}, chatCtx []types.Message,
req types.ApiRequest, req types.ApiRequest,
userVo vo.User, userVo vo.User,
ctx context.Context, ctx context.Context,
@@ -107,9 +107,6 @@ func (h *ChatHandler) sendChatGLMMessage(
// 消息发送成功 // 消息发送成功
if len(contents) > 0 { if len(contents) > 0 {
// 更新用户的对话次数
h.subUserCalls(userVo, session)
if message.Role == "" { if message.Role == "" {
message.Role = "assistant" message.Role = "assistant"
} }
@@ -150,8 +147,8 @@ func (h *ChatHandler) sendChatGLMMessage(
// for reply // for reply
// 计算本次对话消耗的总 token 数量 // 计算本次对话消耗的总 token 数量
replyToken, _ := utils.CalcTokens(message.Content, req.Model) replyTokens, _ := utils.CalcTokens(message.Content, req.Model)
totalTokens := replyToken + getTotalTokens(req) totalTokens := replyTokens + getTotalTokens(req)
historyReplyMsg := model.ChatMessage{ historyReplyMsg := model.ChatMessage{
UserId: userVo.Id, UserId: userVo.Id,
ChatId: session.ChatId, ChatId: session.ChatId,
@@ -169,8 +166,9 @@ func (h *ChatHandler) sendChatGLMMessage(
if res.Error != nil { if res.Error != nil {
logger.Error("failed to save reply history message: ", res.Error) logger.Error("failed to save reply history message: ", res.Error)
} }
// 更新用户信息
h.incUserTokenFee(userVo.Id, totalTokens) // 更新用户算力
h.subUserPower(userVo, session, promptToken, replyTokens)
} }
// 保存当前会话 // 保存当前会话
+13 -14
View File
@@ -20,7 +20,7 @@ import (
// OPenAI 消息发送实现 // OPenAI 消息发送实现
func (h *ChatHandler) sendOpenAiMessage( func (h *ChatHandler) sendOpenAiMessage(
chatCtx []interface{}, chatCtx []types.Message,
req types.ApiRequest, req types.ApiRequest,
userVo vo.User, userVo vo.User,
ctx context.Context, ctx context.Context,
@@ -46,8 +46,10 @@ func (h *ChatHandler) sendOpenAiMessage(
utils.ReplyMessage(ws, ErrorMsg) utils.ReplyMessage(ws, ErrorMsg)
utils.ReplyMessage(ws, ErrImg) utils.ReplyMessage(ws, ErrImg)
all, _ := io.ReadAll(response.Body) if response.Body != nil {
logger.Error(string(all)) all, _ := io.ReadAll(response.Body)
logger.Error(string(all))
}
return err return err
} else { } else {
defer response.Body.Close() defer response.Body.Close()
@@ -171,9 +173,6 @@ func (h *ChatHandler) sendOpenAiMessage(
// 消息发送成功 // 消息发送成功
if len(contents) > 0 { if len(contents) > 0 {
// 更新用户的对话次数
h.subUserCalls(userVo, session)
if message.Role == "" { if message.Role == "" {
message.Role = "assistant" message.Role = "assistant"
} }
@@ -218,16 +217,16 @@ func (h *ChatHandler) sendOpenAiMessage(
} }
// 计算本次对话消耗的总 token 数量 // 计算本次对话消耗的总 token 数量
var totalTokens = 0 var replyTokens = 0
if toolCall { // prompt + 函数名 + 参数 token if toolCall { // prompt + 函数名 + 参数 token
tokens, _ := utils.CalcTokens(function.Name, req.Model) tokens, _ := utils.CalcTokens(function.Name, req.Model)
totalTokens += tokens replyTokens += tokens
tokens, _ = utils.CalcTokens(utils.InterfaceToString(arguments), req.Model) tokens, _ = utils.CalcTokens(utils.InterfaceToString(arguments), req.Model)
totalTokens += tokens replyTokens += tokens
} else { } else {
totalTokens, _ = utils.CalcTokens(message.Content, req.Model) replyTokens, _ = utils.CalcTokens(message.Content, req.Model)
} }
totalTokens += getTotalTokens(req) replyTokens += getTotalTokens(req)
historyReplyMsg := model.ChatMessage{ historyReplyMsg := model.ChatMessage{
UserId: userVo.Id, UserId: userVo.Id,
@@ -236,7 +235,7 @@ func (h *ChatHandler) sendOpenAiMessage(
Type: types.ReplyMsg, Type: types.ReplyMsg,
Icon: role.Icon, Icon: role.Icon,
Content: h.extractImgUrl(message.Content), Content: h.extractImgUrl(message.Content),
Tokens: totalTokens, Tokens: replyTokens,
UseContext: useContext, UseContext: useContext,
Model: req.Model, Model: req.Model,
} }
@@ -247,8 +246,8 @@ func (h *ChatHandler) sendOpenAiMessage(
logger.Error("failed to save reply history message: ", res.Error) logger.Error("failed to save reply history message: ", res.Error)
} }
// 更新用户信息 // 更新用户算力
h.incUserTokenFee(userVo.Id, totalTokens) h.subUserPower(userVo, session, promptToken, replyTokens)
} }
// 保存当前会话 // 保存当前会话
+6 -8
View File
@@ -31,7 +31,7 @@ type qWenResp struct {
// 通义千问消息发送实现 // 通义千问消息发送实现
func (h *ChatHandler) sendQWenMessage( func (h *ChatHandler) sendQWenMessage(
chatCtx []interface{}, chatCtx []types.Message,
req types.ApiRequest, req types.ApiRequest,
userVo vo.User, userVo vo.User,
ctx context.Context, ctx context.Context,
@@ -128,9 +128,6 @@ func (h *ChatHandler) sendQWenMessage(
// 消息发送成功 // 消息发送成功
if len(contents) > 0 { if len(contents) > 0 {
// 更新用户的对话次数
h.subUserCalls(userVo, session)
if message.Role == "" { if message.Role == "" {
message.Role = "assistant" message.Role = "assistant"
} }
@@ -171,8 +168,8 @@ func (h *ChatHandler) sendQWenMessage(
// for reply // for reply
// 计算本次对话消耗的总 token 数量 // 计算本次对话消耗的总 token 数量
replyToken, _ := utils.CalcTokens(message.Content, req.Model) replyTokens, _ := utils.CalcTokens(message.Content, req.Model)
totalTokens := replyToken + getTotalTokens(req) totalTokens := replyTokens + getTotalTokens(req)
historyReplyMsg := model.ChatMessage{ historyReplyMsg := model.ChatMessage{
UserId: userVo.Id, UserId: userVo.Id,
ChatId: session.ChatId, ChatId: session.ChatId,
@@ -190,8 +187,9 @@ func (h *ChatHandler) sendQWenMessage(
if res.Error != nil { if res.Error != nil {
logger.Error("failed to save reply history message: ", res.Error) logger.Error("failed to save reply history message: ", res.Error)
} }
// 更新用户信息
h.incUserTokenFee(userVo.Id, totalTokens) // 更新用户算力
h.subUserPower(userVo, session, promptToken, replyTokens)
} }
// 保存当前会话 // 保存当前会话
+6 -8
View File
@@ -58,7 +58,7 @@ var Model2URL = map[string]string{
// 科大讯飞消息发送实现 // 科大讯飞消息发送实现
func (h *ChatHandler) sendXunFeiMessage( func (h *ChatHandler) sendXunFeiMessage(
chatCtx []interface{}, chatCtx []types.Message,
req types.ApiRequest, req types.ApiRequest,
userVo vo.User, userVo vo.User,
ctx context.Context, ctx context.Context,
@@ -166,9 +166,6 @@ func (h *ChatHandler) sendXunFeiMessage(
// 消息发送成功 // 消息发送成功
if len(contents) > 0 { if len(contents) > 0 {
// 更新用户的对话次数
h.subUserCalls(userVo, session)
if message.Role == "" { if message.Role == "" {
message.Role = "assistant" message.Role = "assistant"
} }
@@ -209,8 +206,8 @@ func (h *ChatHandler) sendXunFeiMessage(
// for reply // for reply
// 计算本次对话消耗的总 token 数量 // 计算本次对话消耗的总 token 数量
replyToken, _ := utils.CalcTokens(message.Content, req.Model) replyTokens, _ := utils.CalcTokens(message.Content, req.Model)
totalTokens := replyToken + getTotalTokens(req) totalTokens := replyTokens + getTotalTokens(req)
historyReplyMsg := model.ChatMessage{ historyReplyMsg := model.ChatMessage{
UserId: userVo.Id, UserId: userVo.Id,
ChatId: session.ChatId, ChatId: session.ChatId,
@@ -228,8 +225,9 @@ func (h *ChatHandler) sendXunFeiMessage(
if res.Error != nil { if res.Error != nil {
logger.Error("failed to save reply history message: ", res.Error) logger.Error("failed to save reply history message: ", res.Error)
} }
// 更新用户信息
h.incUserTokenFee(userVo.Id, totalTokens) // 更新用户算力
h.subUserPower(userVo, session, promptToken, replyTokens)
} }
// 保存当前会话 // 保存当前会话
+18 -5
View File
@@ -192,7 +192,6 @@ func (h *FunctionHandler) Dall3(c *gin.Context) {
} }
logger.Debugf("绘画参数:%+v", params) logger.Debugf("绘画参数:%+v", params)
// check img calls
var user model.User var user model.User
tx := h.db.Where("id = ?", params["user_id"]).First(&user) tx := h.db.Where("id = ?", params["user_id"]).First(&user)
if tx.Error != nil { if tx.Error != nil {
@@ -200,8 +199,8 @@ func (h *FunctionHandler) Dall3(c *gin.Context) {
return return
} }
if user.ImgCalls <= 0 { if user.Power < h.App.SysConfig.DallPower {
resp.ERROR(c, "当前用户的绘图次数额度不足") resp.ERROR(c, "当前用户剩余算力不足以完成本次绘画")
return return
} }
@@ -274,8 +273,22 @@ func (h *FunctionHandler) Dall3(c *gin.Context) {
} }
content := fmt.Sprintf("下面是根据您的描述创作的图片,它描绘了 【%s】 的场景。 \n\n![](%s)\n", prompt, imgURL) 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) resp.SUCCESS(c, content)
} }
+44 -15
View File
@@ -48,8 +48,8 @@ func (h *MidJourneyHandler) preCheck(c *gin.Context) bool {
return false return false
} }
if user.ImgCalls <= 0 { if user.Power < h.App.SysConfig.MjPower {
resp.ERROR(c, "您的绘图次数不足,请联系管理员充值") resp.ERROR(c, "当前用户剩余算力不足以完成本次绘画")
return false return false
} }
@@ -160,13 +160,18 @@ func (h *MidJourneyHandler) Image(c *gin.Context) {
TaskId: taskId, TaskId: taskId,
Progress: 0, Progress: 0,
Prompt: prompt, Prompt: prompt,
Power: h.App.SysConfig.MjPower,
CreatedAt: time.Now(), CreatedAt: time.Now(),
} }
opt := "绘图"
if data.TaskType == types.TaskBlend.String() { 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() { } 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 { if res := h.db.Create(&job); res.Error != nil || res.RowsAffected == 0 {
resp.ERROR(c, "添加任务失败:"+res.Error.Error()) resp.ERROR(c, "添加任务失败:"+res.Error.Error())
return return
@@ -187,8 +192,23 @@ func (h *MidJourneyHandler) Image(c *gin.Context) {
_ = client.Send([]byte("Task Updated")) _ = client.Send([]byte("Task Updated"))
} }
// update user's img calls // update user's power
h.db.Model(&model.User{}).Where("id = ?", job.UserId).UpdateColumn("img_calls", gorm.Expr("img_calls - ?", 1)) 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) resp.SUCCESS(c)
} }
@@ -276,6 +296,7 @@ func (h *MidJourneyHandler) Variation(c *gin.Context) {
TaskId: taskId, TaskId: taskId,
Progress: 0, Progress: 0,
Prompt: data.Prompt, Prompt: data.Prompt,
Power: h.App.SysConfig.MjPower,
CreatedAt: time.Now(), CreatedAt: time.Now(),
} }
if res := h.db.Create(&job); res.Error != nil || res.RowsAffected == 0 { 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")) _ = client.Send([]byte("Task Updated"))
} }
// update user's img calls // update user's power
h.db.Model(&model.User{}).Where("id = ?", job.UserId).UpdateColumn("img_calls", gorm.Expr("img_calls - ?", 1)) 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) resp.SUCCESS(c)
} }
@@ -368,13 +404,6 @@ func (h *MidJourneyHandler) getData(finish bool, userId uint, page int, pageSize
continue 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 != "" { if item.Progress < 100 && item.ImgURL == "" && item.OrgURL != "" {
// discord 服务器图片需要使用代理转发图片数据流 // discord 服务器图片需要使用代理转发图片数据流
if strings.HasPrefix(item.OrgURL, "https://cdn.discordapp.com") { if strings.HasPrefix(item.OrgURL, "https://cdn.discordapp.com") {
+5 -10
View File
@@ -202,8 +202,7 @@ func (h *PaymentHandler) PayQrcode(c *gin.Context) {
// 创建订单 // 创建订单
remark := types.OrderRemark{ remark := types.OrderRemark{
Days: product.Days, Days: product.Days,
Calls: product.Calls, Power: product.Power,
ImgCalls: product.ImgCalls,
Name: product.Name, Name: product.Name,
Price: product.Price, Price: product.Price,
Discount: product.Discount, Discount: product.Discount,
@@ -313,20 +312,16 @@ func (h *PaymentHandler) notify(orderNo string, tradeNo string) error {
if remark.Days > 0 { // 只延期 VIP,不增加调用次数 if remark.Days > 0 { // 只延期 VIP,不增加调用次数
user.ExpiredTime = time.Unix(user.ExpiredTime, 0).AddDate(0, 0, remark.Days).Unix() user.ExpiredTime = time.Unix(user.ExpiredTime, 0).AddDate(0, 0, remark.Days).Unix()
} else { // 充值点卡,直接增加次数即可 } else { // 充值点卡,直接增加次数即可
user.Calls += remark.Calls user.Power += remark.Power
user.ImgCalls += remark.ImgCalls
} }
} else { // 非 VIP 用户 } else { // 非 VIP 用户
if remark.Days > 0 { // vip 套餐:days > 0, calls == 0 if remark.Days > 0 { // vip 套餐:days > 0, power == 0
user.ExpiredTime = time.Now().AddDate(0, 0, remark.Days).Unix() user.ExpiredTime = time.Now().AddDate(0, 0, remark.Days).Unix()
user.Calls += h.App.SysConfig.VipMonthCalls user.Power += h.App.SysConfig.VipMonthPower
user.ImgCalls += h.App.SysConfig.VipMonthImgCalls
user.Vip = true user.Vip = true
} else { //点卡:days == 0, calls > 0 } else { //点卡:days == 0, calls > 0
user.Calls += remark.Calls user.Power += remark.Power
user.ImgCalls += remark.ImgCalls
} }
} }
+19 -11
View File
@@ -7,11 +7,13 @@ import (
"chatplus/store/vo" "chatplus/store/vo"
"chatplus/utils" "chatplus/utils"
"chatplus/utils/resp" "chatplus/utils/resp"
"fmt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"gorm.io/gorm" "gorm.io/gorm"
"math" "math"
"strings" "strings"
"sync" "sync"
"time"
) )
type RewardHandler struct { type RewardHandler struct {
@@ -30,7 +32,6 @@ func NewRewardHandler(server *core.AppServer, db *gorm.DB) *RewardHandler {
func (h *RewardHandler) Verify(c *gin.Context) { func (h *RewardHandler) Verify(c *gin.Context) {
var data struct { var data struct {
TxId string `json:"tx_id"` TxId string `json:"tx_id"`
Type string `json:"type"`
} }
if err := c.ShouldBindJSON(&data); err != nil { if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs) resp.ERROR(c, types.InvalidArgs)
@@ -63,16 +64,11 @@ func (h *RewardHandler) Verify(c *gin.Context) {
tx := h.db.Begin() tx := h.db.Begin()
exchange := vo.RewardExchange{} exchange := vo.RewardExchange{}
if data.Type == "chat" { power := math.Ceil(item.Amount / h.App.SysConfig.PowerPrice)
calls := math.Ceil(item.Amount / h.App.SysConfig.ChatCallPrice) exchange.Power = int(power)
exchange.Calls = int(calls) res = tx.Model(&user).UpdateColumn("power", gorm.Expr("power + ?", exchange.Power))
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))
}
if res.Error != nil { if res.Error != nil {
tx.Rollback()
resp.ERROR(c, "更新数据库失败!") resp.ERROR(c, "更新数据库失败!")
return return
} }
@@ -81,13 +77,25 @@ func (h *RewardHandler) Verify(c *gin.Context) {
item.Status = true item.Status = true
item.UserId = user.Id item.UserId = user.Id
item.Exchange = utils.JsonEncode(exchange) item.Exchange = utils.JsonEncode(exchange)
res = h.db.Updates(&item) res = tx.Updates(&item)
if res.Error != nil { if res.Error != nil {
tx.Rollback() tx.Rollback()
resp.ERROR(c, "更新数据库失败!") resp.ERROR(c, "更新数据库失败!")
return 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() tx.Commit()
resp.SUCCESS(c) resp.SUCCESS(c)
+20 -15
View File
@@ -72,8 +72,8 @@ func (h *SdJobHandler) checkLimits(c *gin.Context) bool {
return false return false
} }
if user.ImgCalls <= 0 { if user.Power < h.App.SysConfig.SdPower {
resp.ERROR(c, "您的绘图次数不足,请联系管理员充值") resp.ERROR(c, "当前用户剩余算力不足以完成本次绘画")
return false return false
} }
@@ -140,6 +140,7 @@ func (h *SdJobHandler) Image(c *gin.Context) {
Params: utils.JsonEncode(params), Params: utils.JsonEncode(params),
Prompt: data.Prompt, Prompt: data.Prompt,
Progress: 0, Progress: 0,
Power: h.App.SysConfig.SdPower,
CreatedAt: time.Now(), CreatedAt: time.Now(),
} }
res := h.db.Create(&job) res := h.db.Create(&job)
@@ -162,8 +163,23 @@ func (h *SdJobHandler) Image(c *gin.Context) {
_ = client.Send([]byte("Task Updated")) _ = client.Send([]byte("Task Updated"))
} }
// update user's img calls // update user's power
h.db.Model(&model.User{}).Where("id = ?", job.UserId).UpdateColumn("img_calls", gorm.Expr("img_calls - ?", 1)) 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) resp.SUCCESS(c)
} }
@@ -232,18 +248,7 @@ func (h *SdJobHandler) getData(finish bool, userId uint, page int, pageSize int,
continue continue
} }
if job.Progress == -1 {
h.db.Delete(&model.SdJob{Id: job.Id})
}
if item.Progress < 100 { 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, "") image, err := utils.DownloadImage(item.ImgURL, "")
if err == nil { if err == nil {
+5 -12
View File
@@ -102,8 +102,7 @@ func (h *UserHandler) Register(c *gin.Context) {
types.ChatGLM: "", types.ChatGLM: "",
}, },
}), }),
Calls: h.App.SysConfig.InitChatCalls, Power: h.App.SysConfig.InitPower,
ImgCalls: h.App.SysConfig.InitImgCalls,
} }
res = h.db.Create(&user) res = h.db.Create(&user)
@@ -117,11 +116,8 @@ func (h *UserHandler) Register(c *gin.Context) {
if data.InviteCode != "" { if data.InviteCode != "" {
// 增加邀请数量 // 增加邀请数量
h.db.Model(&model.InviteCode{}).Where("code = ?", data.InviteCode).UpdateColumn("reg_num", gorm.Expr("reg_num + ?", 1)) h.db.Model(&model.InviteCode{}).Where("code = ?", data.InviteCode).UpdateColumn("reg_num", gorm.Expr("reg_num + ?", 1))
if h.App.SysConfig.InviteChatCalls > 0 { if h.App.SysConfig.InvitePower > 0 {
h.db.Model(&model.User{}).Where("id = ?", inviteCode.UserId).UpdateColumn("calls", gorm.Expr("calls + ?", h.App.SysConfig.InviteChatCalls)) h.db.Model(&model.User{}).Where("id = ?", inviteCode.UserId).UpdateColumn("power", gorm.Expr("power + ?", h.App.SysConfig.InvitePower))
}
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))
} }
// 添加邀请记录 // 添加邀请记录
@@ -130,7 +126,7 @@ func (h *UserHandler) Register(c *gin.Context) {
UserId: user.Id, UserId: user.Id,
Username: user.Username, Username: user.Username,
InviteCode: inviteCode.Code, InviteCode: inviteCode.Code,
Reward: utils.JsonEncode(types.InviteReward{ChatCalls: h.App.SysConfig.InviteChatCalls, ImgCalls: h.App.SysConfig.InviteImgCalls}), Remark: fmt.Sprintf("奖励 %d 算力", h.App.SysConfig.InvitePower),
}) })
} }
@@ -254,10 +250,7 @@ type userProfile struct {
Username string `json:"username"` Username string `json:"username"`
Avatar string `json:"avatar"` Avatar string `json:"avatar"`
ChatConfig types.UserChatConfig `json:"chat_config"` ChatConfig types.UserChatConfig `json:"chat_config"`
Calls int `json:"calls"` Power int `json:"power"`
ImgCalls int `json:"img_calls"`
TotalTokens int64 `json:"total_tokens"`
Tokens int `json:"tokens"`
ExpiredTime int64 `json:"expired_time"` ExpiredTime int64 `json:"expired_time"`
Vip bool `json:"vip"` Vip bool `json:"vip"`
} }
-6
View File
@@ -96,12 +96,6 @@ func (s *Service) Run() {
s.db.Updates(&job) s.db.Updates(&job)
// 任务失败,通知前端 // 任务失败,通知前端
s.notifyQueue.RPush(task.UserId) 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 continue
} }
logger.Infof("任务提交成功:%+v", res) logger.Infof("任务提交成功:%+v", res)
+29 -14
View File
@@ -191,28 +191,43 @@ func (p *ServicePool) SyncTaskProgress() {
go func() { go func() {
var items []model.MidJourneyJob var items []model.MidJourneyJob
for { for {
res := p.db.Where("progress >= ? AND progress < ?", 0, 100).Find(&items) res := p.db.Where("progress < ?", 100).Find(&items)
if res.Error != nil { if res.Error != nil {
continue continue
} }
for _, v := range items { for _, job := range items {
// 30 分钟还没完成的任务直接删除 // 失败或者 30 分钟还没完成的任务删除并退回算力
if time.Now().Sub(v.CreatedAt) > time.Minute*30 { if time.Now().Sub(job.CreatedAt) > time.Minute*30 || job.Progress == -1 {
p.db.Delete(&v) p.db.Delete(&job)
// 非放大任务,退回绘图次数 // 略过 Upscale 任务
if v.Type != types.TaskUpscale.String() { if job.Type != types.TaskUpscale.String() {
p.db.Model(&model.User{}).Where("id = ?", v.UserId).UpdateColumn("img_calls", gorm.Expr("img_calls + ?", 1)) 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 continue
} }
if !strings.HasPrefix(v.ChannelId, "mj-service-plus") { if servicePlus := p.getServicePlus(job.ChannelId); servicePlus != nil {
continue _ = servicePlus.Notify(job)
}
if servicePlus := p.getServicePlus(v.ChannelId); servicePlus != nil {
_ = servicePlus.Notify(v)
} }
} }
+1 -1
View File
@@ -84,7 +84,7 @@ func (s *Service) Run() {
if err != nil { if err != nil {
logger.Error("绘画任务执行失败:", err.Error()) logger.Error("绘画任务执行失败:", err.Error())
// update the task progress // 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, "progress": -1,
"err_msg": err.Error(), "err_msg": err.Error(),
}) })
+44
View File
@@ -4,7 +4,9 @@ import (
"chatplus/core/types" "chatplus/core/types"
"chatplus/service/oss" "chatplus/service/oss"
"chatplus/store" "chatplus/store"
"chatplus/store/model"
"fmt" "fmt"
"time"
"github.com/go-redis/redis/v8" "github.com/go-redis/redis/v8"
"gorm.io/gorm" "gorm.io/gorm"
@@ -14,6 +16,7 @@ type ServicePool struct {
services []*Service services []*Service
taskQueue *store.RedisQueue taskQueue *store.RedisQueue
notifyQueue *store.RedisQueue notifyQueue *store.RedisQueue
db *gorm.DB
Clients *types.LMap[uint, *types.WsClient] // UserId => Client 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, taskQueue: taskQueue,
notifyQueue: notifyQueue, notifyQueue: notifyQueue,
services: services, services: services,
db: db,
Clients: types.NewLMap[uint, *types.WsClient](), 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 // HasAvailableService check if it has available mj service in pool
func (p *ServicePool) HasAvailableService() bool { func (p *ServicePool) HasAvailableService() bool {
return len(p.services) > 0 return len(p.services) > 0
-4
View File
@@ -74,8 +74,6 @@ func (s *Service) Run() {
"progress": -1, "progress": -1,
"err_msg": err.Error(), "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 // release task num
atomic.AddInt32(&s.handledTaskNum, -1) atomic.AddInt32(&s.handledTaskNum, -1)
// 通知前端,任务失败 // 通知前端,任务失败
@@ -307,8 +305,6 @@ func (s *Service) callback(data CBReq) {
"progress": -1, "progress": -1,
"err_msg": data.Message, "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))
} }
// 发送更新状态信号 // 发送更新状态信号
+21 -49
View File
@@ -39,7 +39,7 @@ func NewXXLJobExecutor(config *types.AppConfig, db *gorm.DB) *XXLJobExecutor {
func (e *XXLJobExecutor) Run() error { func (e *XXLJobExecutor) Run() error {
e.executor.RegTask("ClearOrders", e.ClearOrders) e.executor.RegTask("ClearOrders", e.ClearOrders)
e.executor.RegTask("ResetVipCalls", e.ResetVipCalls) e.executor.RegTask("ResetVipPower", e.ResetVipPower)
return e.executor.Run() 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) return fmt.Sprintf("Clear order successfully, affect rows: %d", res.RowsAffected)
} }
// ResetVipCalls 清理过期的 VIP 会员 // ResetVipPower 清理过期的 VIP 会员
func (e *XXLJobExecutor) ResetVipCalls(cxt context.Context, param *xxl.RunReq) (msg string) { func (e *XXLJobExecutor) ResetVipPower(cxt context.Context, param *xxl.RunReq) (msg string) {
logger.Info("开始进行月底账号盘点...") logger.Info("开始进行月底账号盘点...")
var users []model.User var users []model.User
res := e.db.Where("vip = ?", 1).Find(&users) 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() 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 { for _, u := range users {
// 账号到期,直接清零 if u.Power <= 0 {
if u.ExpiredTime <= currentTime.Unix() { u.Power = 0
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)
} }
u.Tokens = 0 u.Power += config.VipMonthPower
// update user // 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("月底盘点完成!") logger.Info("月底盘点完成!")
return "success" return "success"
+1 -1
View File
@@ -7,6 +7,6 @@ type ChatModel struct {
Value string // API Key 的值 Value string // API Key 的值
SortNum int SortNum int
Enabled bool Enabled bool
Weight int // 对话权重,每次对话扣减多少次对话额度 Power int // 每次对话消耗算力
Open bool // 是否开放模型给所有人使用 Open bool // 是否开放模型给所有人使用
} }
+1 -1
View File
@@ -10,6 +10,6 @@ type InviteLog struct {
UserId uint UserId uint
Username string Username string
InviteCode string InviteCode string
Reward string `gorm:"column:reward_json"` // 邀请奖励 Remark string
CreatedAt time.Time CreatedAt time.Time
} }
+1
View File
@@ -18,6 +18,7 @@ type MidJourneyJob struct {
UseProxy bool // 是否使用反代加载图片 UseProxy bool // 是否使用反代加载图片
Publish bool //是否发布图片到画廊 Publish bool //是否发布图片到画廊
ErrMsg string // 报错信息 ErrMsg string // 报错信息
Power int // 消耗算力
CreatedAt time.Time CreatedAt time.Time
} }
+20
View File
@@ -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
}
+1 -2
View File
@@ -7,8 +7,7 @@ type Product struct {
Price float64 Price float64
Discount float64 Discount float64
Days int Days int
Calls int Power int
ImgCalls int
Enabled bool Enabled bool
Sales int Sales int
SortNum int SortNum int
+1
View File
@@ -13,6 +13,7 @@ type SdJob struct {
Params string Params string
Publish bool //是否发布图片到画廊 Publish bool //是否发布图片到画廊
ErrMsg string // 报错信息 ErrMsg string // 报错信息
Power int // 消耗算力
CreatedAt time.Time CreatedAt time.Time
} }
+1 -4
View File
@@ -7,9 +7,7 @@ type User struct {
Password string Password string
Avatar string Avatar string
Salt string // 密码盐 Salt string // 密码盐
TotalTokens int64 // 总消耗 tokens Power int // 剩余算力
Calls int // 剩余对话次数
ImgCalls int // 剩余绘图次数
ChatConfig string `gorm:"column:chat_config_json"` // 聊天配置 json ChatConfig string `gorm:"column:chat_config_json"` // 聊天配置 json
ChatRoles string `gorm:"column:chat_roles_json"` // 聊天角色 ChatRoles string `gorm:"column:chat_roles_json"` // 聊天角色
ChatModels string `gorm:"column:chat_models_json"` // AI 模型,不同的用户拥有不同的聊天模型 ChatModels string `gorm:"column:chat_models_json"` // AI 模型,不同的用户拥有不同的聊天模型
@@ -18,5 +16,4 @@ type User struct {
LastLoginAt int64 // 最后登录时间 LastLoginAt int64 // 最后登录时间
LastLoginIp string // 最后登录 IP LastLoginIp string // 最后登录 IP
Vip bool // 是否 VIP 会员 Vip bool // 是否 VIP 会员
Tokens int
} }
+7 -11
View File
@@ -1,15 +1,11 @@
package vo package vo
import (
"chatplus/core/types"
)
type InviteLog struct { type InviteLog struct {
Id uint `json:"id"` Id uint `json:"id"`
InviterId uint `json:"inviter_id"` InviterId uint `json:"inviter_id"`
UserId uint `json:"user_id"` UserId uint `json:"user_id"`
Username string `json:"username"` Username string `json:"username"`
InviteCode string `json:"invite_code"` InviteCode string `json:"invite_code"`
Reward types.InviteReward `json:"reward"` Remark string `json:"remark"`
CreatedAt int64 `json:"created_at"` CreatedAt int64 `json:"created_at"`
} }
+1
View File
@@ -18,5 +18,6 @@ type MidJourneyJob struct {
UseProxy bool `json:"use_proxy"` UseProxy bool `json:"use_proxy"`
Publish bool `json:"publish"` Publish bool `json:"publish"`
ErrMsg string `json:"err_msg"` ErrMsg string `json:"err_msg"`
Power int `json:"power"`
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
} }
+16
View File
@@ -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"`
}
+1 -2
View File
@@ -6,8 +6,7 @@ type Product struct {
Price float64 `json:"price"` Price float64 `json:"price"`
Discount float64 `json:"discount"` Discount float64 `json:"discount"`
Days int `json:"days"` Days int `json:"days"`
Calls int `json:"calls"` Power int `json:"power"`
ImgCalls int `json:"img_calls"`
Enabled bool `json:"enabled"` Enabled bool `json:"enabled"`
Sales int `json:"sales"` Sales int `json:"sales"`
SortNum int `json:"sort_num"` SortNum int `json:"sort_num"`
+1 -2
View File
@@ -12,6 +12,5 @@ type Reward struct {
} }
type RewardExchange struct { type RewardExchange struct {
Calls int `json:"calls"` Power int `json:"calls"`
ImgCalls int `json:"img_calls"`
} }
+1
View File
@@ -16,5 +16,6 @@ type SdJob struct {
Prompt string `json:"prompt"` Prompt string `json:"prompt"`
Publish bool `json:"publish"` Publish bool `json:"publish"`
ErrMsg string `json:"err_msg"` ErrMsg string `json:"err_msg"`
Power int `json:"power"`
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
} }
+2 -5
View File
@@ -7,10 +7,8 @@ type User struct {
Username string `json:"username"` Username string `json:"username"`
Nickname string `json:"nickname"` Nickname string `json:"nickname"`
Avatar string `json:"avatar"` Avatar string `json:"avatar"`
Salt string `json:"salt"` // 密码盐 Salt string `json:"salt"` // 密码盐
TotalTokens int64 `json:"total_tokens"` // 总消耗tokens Power int `json:"calls"` // 剩余算力
Calls int `json:"calls"` // 剩余对话次数
ImgCalls int `json:"img_calls"`
ChatConfig types.UserChatConfig `json:"chat_config"` // 聊天配置 ChatConfig types.UserChatConfig `json:"chat_config"` // 聊天配置
ChatRoles []string `json:"chat_roles"` // 聊天角色集合 ChatRoles []string `json:"chat_roles"` // 聊天角色集合
ChatModels []string `json:"chat_models"` // AI模型集合 ChatModels []string `json:"chat_models"` // AI模型集合
@@ -19,5 +17,4 @@ type User struct {
LastLoginAt int64 `json:"last_login_at"` // 最后登录时间 LastLoginAt int64 `json:"last_login_at"` // 最后登录时间
LastLoginIp string `json:"last_login_ip"` // 最后登录 IP LastLoginIp string `json:"last_login_ip"` // 最后登录 IP
Vip bool `json:"vip"` Vip bool `json:"vip"`
Tokens int `json:"token"` // 当月消耗的 fee
} }
-23
View File
@@ -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`)
+46
View File
@@ -0,0 +1,46 @@
-- 删除用户名重复的用户,只保留一条
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`;
ALTER TABLE `chatgpt_invite_logs` CHANGE `reward_json` `remark` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '备注';
@@ -1,3 +0,0 @@
VITE_PROXY_BASE_URL="/api"
VITE_TARGET_URL="http://172.22.11.2:5678"
VITE_SOCKET_IO_URL="http://172.28.1.3:8899"
+1 -1
View File
@@ -18,4 +18,4 @@ dist
*.ntvs* *.ntvs*
*.njsproj *.njsproj
*.sln *.sln
*.sw? *.sw?
View File
@@ -0,0 +1,3 @@
VITE_PROXY_BASE_URL="/api"
VITE_TARGET_URL="http://localhost:5678"
VITE_SOCKET_IO_URL="http://localhost:8899"

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Some files were not shown because too many files have changed in this diff Show More