fix conflicts
@@ -2,6 +2,9 @@
 | 
			
		||||
## v3.2.8
 | 
			
		||||
* 功能优化:SD 绘画页面采用 websocket 替换 http 轮询机制,节省带宽
 | 
			
		||||
* 功能优化:移动端聊天页面图片支持预览和放大功能
 | 
			
		||||
* 功能优化:MJ 和 SD 页面数据分页加载,解决一次性加载太多数据导致页面卡顿的问题
 | 
			
		||||
* 功能优化:手机端 MJ 增加提示词翻译功能
 | 
			
		||||
* 功能优化:控制台订单管理页面显示未支付订单,并提供订单删除功能。
 | 
			
		||||
* 功能新增:移动端支持充值
 | 
			
		||||
 | 
			
		||||
## v3.2.7
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,7 @@ type AppServer struct {
 | 
			
		||||
	Debug        bool
 | 
			
		||||
	Config       *types.AppConfig
 | 
			
		||||
	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
 | 
			
		||||
	SysConfig  *types.SystemConfig // system config cache
 | 
			
		||||
@@ -47,7 +47,7 @@ func NewServer(appConfig *types.AppConfig) *AppServer {
 | 
			
		||||
		Debug:         false,
 | 
			
		||||
		Config:        appConfig,
 | 
			
		||||
		Engine:        gin.Default(),
 | 
			
		||||
		ChatContexts:  types.NewLMap[string, []interface{}](),
 | 
			
		||||
		ChatContexts:  types.NewLMap[string, []types.Message](),
 | 
			
		||||
		ChatSession:   types.NewLMap[string, *types.ChatSession](),
 | 
			
		||||
		ChatClients:   types.NewLMap[string, *types.WsClient](),
 | 
			
		||||
		ReqCancelFunc: types.NewLMap[string, context.CancelFunc](),
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -158,37 +158,32 @@ type UserChatConfig struct {
 | 
			
		||||
	ApiKeys map[Platform]string `json:"api_keys"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type InviteReward struct {
 | 
			
		||||
	ChatCalls int `json:"chat_calls"`
 | 
			
		||||
	ImgCalls  int `json:"img_calls"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ModelAPIConfig struct {
 | 
			
		||||
	Temperature float32 `json:"temperature"`
 | 
			
		||||
	MaxTokens   int     `json:"max_tokens"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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"` // 微信客服地址
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ type MKey interface {
 | 
			
		||||
	string | int | uint
 | 
			
		||||
}
 | 
			
		||||
type MValue interface {
 | 
			
		||||
	*WsClient | *ChatSession | context.CancelFunc | []interface{}
 | 
			
		||||
	*WsClient | *ChatSession | context.CancelFunc | []Message
 | 
			
		||||
}
 | 
			
		||||
type LMap[K MKey, T MValue] struct {
 | 
			
		||||
	lock sync.RWMutex
 | 
			
		||||
 
 | 
			
		||||
@@ -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"`
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,7 @@ import (
 | 
			
		||||
// 微软 Azure 模型消息发送实现
 | 
			
		||||
 | 
			
		||||
func (h *ChatHandler) sendAzureMessage(
 | 
			
		||||
	chatCtx []interface{},
 | 
			
		||||
	chatCtx []types.Message,
 | 
			
		||||
	req types.ApiRequest,
 | 
			
		||||
	userVo vo.User,
 | 
			
		||||
	ctx context.Context,
 | 
			
		||||
@@ -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)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// 保存当前会话
 | 
			
		||||
 
 | 
			
		||||
@@ -36,7 +36,7 @@ type baiduResp struct {
 | 
			
		||||
// 百度文心一言消息发送实现
 | 
			
		||||
 | 
			
		||||
func (h *ChatHandler) sendBaiduMessage(
 | 
			
		||||
	chatCtx []interface{},
 | 
			
		||||
	chatCtx []types.Message,
 | 
			
		||||
	req types.ApiRequest,
 | 
			
		||||
	userVo vo.User,
 | 
			
		||||
	ctx context.Context,
 | 
			
		||||
@@ -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)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// 保存当前会话
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
@@ -224,6 +224,14 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
 | 
			
		||||
		utils.ReplyMessage(ws, ErrImg)
 | 
			
		||||
		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{
 | 
			
		||||
		Model:  session.Model.Value,
 | 
			
		||||
		Stream: true,
 | 
			
		||||
@@ -252,7 +260,6 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var tools = make([]interface{}, 0)
 | 
			
		||||
		var functions = make([]interface{}, 0)
 | 
			
		||||
		for _, v := range items {
 | 
			
		||||
			var parameters map[string]interface{}
 | 
			
		||||
			err = utils.JsonDecode(v.Parameters, ¶meters)
 | 
			
		||||
@@ -270,20 +277,11 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
 | 
			
		||||
					"required":    required,
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
			functions = append(functions, gin.H{
 | 
			
		||||
				"name":        v.Name,
 | 
			
		||||
				"description": v.Description,
 | 
			
		||||
				"parameters":  parameters,
 | 
			
		||||
				"required":    required,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		//if len(tools) > 0 {
 | 
			
		||||
		//	req.Tools = tools
 | 
			
		||||
		//	req.ToolChoice = "auto"
 | 
			
		||||
		//}
 | 
			
		||||
		if len(functions) > 0 {
 | 
			
		||||
			req.Functions = functions
 | 
			
		||||
		if len(tools) > 0 {
 | 
			
		||||
			req.Tools = tools
 | 
			
		||||
			req.ToolChoice = "auto"
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	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.ChatContexts.Has(session.ChatId) {
 | 
			
		||||
			chatCtx = h.App.ChatContexts.Get(session.ChatId)
 | 
			
		||||
			messages = h.App.ChatContexts.Get(session.ChatId)
 | 
			
		||||
		} else {
 | 
			
		||||
			// calculate the tokens of current request, to prevent to exceeding the max tokens num
 | 
			
		||||
			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
 | 
			
		||||
			_ = utils.JsonDecode(role.Context, &messages)
 | 
			
		||||
			if chatConfig.ContextDeep > 0 {
 | 
			
		||||
				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 {
 | 
			
		||||
					for i := len(historyMessages) - 1; i >= 0; 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}
 | 
			
		||||
						if msg.Type == types.ReplyMsg {
 | 
			
		||||
							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)
 | 
			
		||||
	}
 | 
			
		||||
	reqMgs := make([]interface{}, 0)
 | 
			
		||||
@@ -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回复消息中生成的图片链接下载到本地
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,7 @@ import (
 | 
			
		||||
// 清华大学 ChatGML 消息发送实现
 | 
			
		||||
 | 
			
		||||
func (h *ChatHandler) sendChatGLMMessage(
 | 
			
		||||
	chatCtx []interface{},
 | 
			
		||||
	chatCtx []types.Message,
 | 
			
		||||
	req types.ApiRequest,
 | 
			
		||||
	userVo vo.User,
 | 
			
		||||
	ctx context.Context,
 | 
			
		||||
@@ -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)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// 保存当前会话
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,7 @@ import (
 | 
			
		||||
 | 
			
		||||
// OPenAI 消息发送实现
 | 
			
		||||
func (h *ChatHandler) sendOpenAiMessage(
 | 
			
		||||
	chatCtx []interface{},
 | 
			
		||||
	chatCtx []types.Message,
 | 
			
		||||
	req types.ApiRequest,
 | 
			
		||||
	userVo vo.User,
 | 
			
		||||
	ctx context.Context,
 | 
			
		||||
@@ -46,8 +46,10 @@ func (h *ChatHandler) sendOpenAiMessage(
 | 
			
		||||
 | 
			
		||||
		utils.ReplyMessage(ws, ErrorMsg)
 | 
			
		||||
		utils.ReplyMessage(ws, ErrImg)
 | 
			
		||||
		all, _ := io.ReadAll(response.Body)
 | 
			
		||||
		logger.Error(string(all))
 | 
			
		||||
		if response.Body != nil {
 | 
			
		||||
			all, _ := io.ReadAll(response.Body)
 | 
			
		||||
			logger.Error(string(all))
 | 
			
		||||
		}
 | 
			
		||||
		return err
 | 
			
		||||
	} else {
 | 
			
		||||
		defer response.Body.Close()
 | 
			
		||||
@@ -171,9 +173,6 @@ func (h *ChatHandler) sendOpenAiMessage(
 | 
			
		||||
 | 
			
		||||
		// 消息发送成功
 | 
			
		||||
		if len(contents) > 0 {
 | 
			
		||||
			// 更新用户的对话次数
 | 
			
		||||
			h.subUserCalls(userVo, session)
 | 
			
		||||
 | 
			
		||||
			if message.Role == "" {
 | 
			
		||||
				message.Role = "assistant"
 | 
			
		||||
			}
 | 
			
		||||
@@ -218,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,
 | 
			
		||||
@@ -236,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,
 | 
			
		||||
				}
 | 
			
		||||
@@ -247,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)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// 保存当前会话
 | 
			
		||||
 
 | 
			
		||||
@@ -31,7 +31,7 @@ type qWenResp struct {
 | 
			
		||||
 | 
			
		||||
// 通义千问消息发送实现
 | 
			
		||||
func (h *ChatHandler) sendQWenMessage(
 | 
			
		||||
	chatCtx []interface{},
 | 
			
		||||
	chatCtx []types.Message,
 | 
			
		||||
	req types.ApiRequest,
 | 
			
		||||
	userVo vo.User,
 | 
			
		||||
	ctx context.Context,
 | 
			
		||||
@@ -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)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// 保存当前会话
 | 
			
		||||
 
 | 
			
		||||
@@ -58,7 +58,7 @@ var Model2URL = map[string]string{
 | 
			
		||||
// 科大讯飞消息发送实现
 | 
			
		||||
 | 
			
		||||
func (h *ChatHandler) sendXunFeiMessage(
 | 
			
		||||
	chatCtx []interface{},
 | 
			
		||||
	chatCtx []types.Message,
 | 
			
		||||
	req types.ApiRequest,
 | 
			
		||||
	userVo vo.User,
 | 
			
		||||
	ctx context.Context,
 | 
			
		||||
@@ -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)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// 保存当前会话
 | 
			
		||||
 
 | 
			
		||||
@@ -192,7 +192,6 @@ func (h *FunctionHandler) Dall3(c *gin.Context) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logger.Debugf("绘画参数:%+v", params)
 | 
			
		||||
	// check img calls
 | 
			
		||||
	var user model.User
 | 
			
		||||
	tx := h.db.Where("id = ?", params["user_id"]).First(&user)
 | 
			
		||||
	if tx.Error != nil {
 | 
			
		||||
@@ -200,8 +199,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
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -274,8 +273,22 @@ func (h *FunctionHandler) Dall3(c *gin.Context) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	content := fmt.Sprintf("下面是根据您的描述创作的图片,它描绘了 【%s】 的场景。 \n\n\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)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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") {
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
		if remark.Days > 0 { // vip 套餐:days > 0, power == 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
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -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}),
 | 
			
		||||
			Remark:     fmt.Sprintf("奖励 %d 算力", 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"`
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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(),
 | 
			
		||||
			})
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 发送更新状态信号
 | 
			
		||||
 
 | 
			
		||||
@@ -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"
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,6 @@ type ChatModel struct {
 | 
			
		||||
	Value    string // API Key 的值
 | 
			
		||||
	SortNum  int
 | 
			
		||||
	Enabled  bool
 | 
			
		||||
	Weight   int  // 对话权重,每次对话扣减多少次对话额度
 | 
			
		||||
	Power    int  // 每次对话消耗算力
 | 
			
		||||
	Open     bool // 是否开放模型给所有人使用
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,6 @@ type InviteLog struct {
 | 
			
		||||
	UserId     uint
 | 
			
		||||
	Username   string
 | 
			
		||||
	InviteCode string
 | 
			
		||||
	Reward     string `gorm:"column:reward_json"` // 邀请奖励
 | 
			
		||||
	Remark     string
 | 
			
		||||
	CreatedAt  time.Time
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,7 @@ type MidJourneyJob struct {
 | 
			
		||||
	UseProxy    bool   // 是否使用反代加载图片
 | 
			
		||||
	Publish     bool   //是否发布图片到画廊
 | 
			
		||||
	ErrMsg      string // 报错信息
 | 
			
		||||
	Power       int    // 消耗算力
 | 
			
		||||
	CreatedAt   time.Time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										20
									
								
								api/store/model/power_log.go
									
									
									
									
									
										Normal 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
 | 
			
		||||
}
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,7 @@ type SdJob struct {
 | 
			
		||||
	Params    string
 | 
			
		||||
	Publish   bool   //是否发布图片到画廊
 | 
			
		||||
	ErrMsg    string // 报错信息
 | 
			
		||||
	Power     int    // 消耗算力
 | 
			
		||||
	CreatedAt time.Time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,11 @@
 | 
			
		||||
package vo
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"chatplus/core/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type InviteLog struct {
 | 
			
		||||
	Id         uint               `json:"id"`
 | 
			
		||||
	InviterId  uint               `json:"inviter_id"`
 | 
			
		||||
	UserId     uint               `json:"user_id"`
 | 
			
		||||
	Username   string             `json:"username"`
 | 
			
		||||
	InviteCode string             `json:"invite_code"`
 | 
			
		||||
	Reward     types.InviteReward `json:"reward"`
 | 
			
		||||
	CreatedAt  int64              `json:"created_at"`
 | 
			
		||||
	Id         uint   `json:"id"`
 | 
			
		||||
	InviterId  uint   `json:"inviter_id"`
 | 
			
		||||
	UserId     uint   `json:"user_id"`
 | 
			
		||||
	Username   string `json:"username"`
 | 
			
		||||
	InviteCode string `json:"invite_code"`
 | 
			
		||||
	Remark     string `json:"remark"`
 | 
			
		||||
	CreatedAt  int64  `json:"created_at"`
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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"`
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										16
									
								
								api/store/vo/power_log.go
									
									
									
									
									
										Normal 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"`
 | 
			
		||||
}
 | 
			
		||||
@@ -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"`
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,5 @@ type Reward struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type RewardExchange struct {
 | 
			
		||||
	Calls    int `json:"calls"`
 | 
			
		||||
	ImgCalls int `json:"img_calls"`
 | 
			
		||||
	Power int `json:"calls"`
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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"`
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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
									
								
								database/update-v4.0.0.sql
									
									
									
									
									
										Normal 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"
 | 
			
		||||
							
								
								
									
										2
									
								
								gpt-vue/.gitignore → new-ui/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -18,4 +18,4 @@ dist
 | 
			
		||||
*.ntvs*
 | 
			
		||||
*.njsproj
 | 
			
		||||
*.sln
 | 
			
		||||
*.sw?
 | 
			
		||||
*.sw?
 | 
			
		||||
							
								
								
									
										3
									
								
								new-ui/projects/vue-admin/.env.development
									
									
									
									
									
										Normal 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  |