mirror of
				https://github.com/yangjian102621/geekai.git
				synced 2025-11-04 16:23:42 +08:00 
			
		
		
		
	重构主体工作完成
This commit is contained in:
		@@ -30,8 +30,7 @@ type AppServer struct {
 | 
			
		||||
	Engine       *gin.Engine
 | 
			
		||||
	ChatContexts *types.LMap[string, []types.Message] // 聊天上下文 Map [chatId] => []Message
 | 
			
		||||
 | 
			
		||||
	ChatConfig *types.ChatConfig   // chat config cache
 | 
			
		||||
	SysConfig  *types.SystemConfig // system config cache
 | 
			
		||||
	SysConfig *types.SystemConfig // system config cache
 | 
			
		||||
 | 
			
		||||
	// 保存 Websocket 会话 UserId, 每个 UserId 只能连接一次
 | 
			
		||||
	// 防止第三方直接连接 socket 调用 OpenAI API
 | 
			
		||||
@@ -69,23 +68,13 @@ func (s *AppServer) Init(debug bool, client *redis.Client) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *AppServer) Run(db *gorm.DB) error {
 | 
			
		||||
	// load chat config from database
 | 
			
		||||
	var chatConfig model.Config
 | 
			
		||||
	res := db.Where("marker", "chat").First(&chatConfig)
 | 
			
		||||
	if res.Error != nil {
 | 
			
		||||
		return res.Error
 | 
			
		||||
	}
 | 
			
		||||
	err := utils.JsonDecode(chatConfig.Config, &s.ChatConfig)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	// load system configs
 | 
			
		||||
	var sysConfig model.Config
 | 
			
		||||
	res = db.Where("marker", "system").First(&sysConfig)
 | 
			
		||||
	res := db.Where("marker", "system").First(&sysConfig)
 | 
			
		||||
	if res.Error != nil {
 | 
			
		||||
		return res.Error
 | 
			
		||||
	}
 | 
			
		||||
	err = utils.JsonDecode(sysConfig.Config, &s.SysConfig)
 | 
			
		||||
	err := utils.JsonDecode(sysConfig.Config, &s.SysConfig)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -54,10 +54,13 @@ type ChatSession struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ChatModel struct {
 | 
			
		||||
	Id       uint     `json:"id"`
 | 
			
		||||
	Platform Platform `json:"platform"`
 | 
			
		||||
	Value    string   `json:"value"`
 | 
			
		||||
	Power    int      `json:"power"`
 | 
			
		||||
	Id          uint     `json:"id"`
 | 
			
		||||
	Platform    Platform `json:"platform"`
 | 
			
		||||
	Value       string   `json:"value"`
 | 
			
		||||
	Power       int      `json:"power"`
 | 
			
		||||
	MaxTokens   int      `json:"max_tokens"`  // 最大响应长度
 | 
			
		||||
	MaxContext  int      `json:"max_context"` // 最大上下文长度
 | 
			
		||||
	Temperature float32  `json:"temperature"` // 模型温度
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ApiError struct {
 | 
			
		||||
@@ -72,27 +75,6 @@ type ApiError struct {
 | 
			
		||||
const PromptMsg = "prompt" // prompt message
 | 
			
		||||
const ReplyMsg = "reply"   // reply message
 | 
			
		||||
 | 
			
		||||
var ModelToTokens = map[string]int{
 | 
			
		||||
	"gpt-3.5-turbo":     4096,
 | 
			
		||||
	"gpt-3.5-turbo-16k": 16384,
 | 
			
		||||
	"gpt-4":             8192,
 | 
			
		||||
	"gpt-4-32k":         32768,
 | 
			
		||||
	"chatglm_pro":       32768, // 清华智普
 | 
			
		||||
	"chatglm_std":       16384,
 | 
			
		||||
	"chatglm_lite":      4096,
 | 
			
		||||
	"ernie_bot_turbo":   8192, // 文心一言
 | 
			
		||||
	"general":           8192, // 科大讯飞
 | 
			
		||||
	"general2":          8192,
 | 
			
		||||
	"general3":          8192,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetModelMaxToken(model string) int {
 | 
			
		||||
	if token, ok := ModelToTokens[model]; ok {
 | 
			
		||||
		return token
 | 
			
		||||
	}
 | 
			
		||||
	return 4096
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PowerType 算力日志类型
 | 
			
		||||
type PowerType int
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -121,20 +121,6 @@ func (c RedisConfig) Url() string {
 | 
			
		||||
	return fmt.Sprintf("%s:%d", c.Host, c.Port)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ChatConfig 系统默认的聊天配置
 | 
			
		||||
type ChatConfig struct {
 | 
			
		||||
	OpenAI  ModelAPIConfig `json:"open_ai"`
 | 
			
		||||
	Azure   ModelAPIConfig `json:"azure"`
 | 
			
		||||
	ChatGML ModelAPIConfig `json:"chat_gml"`
 | 
			
		||||
	Baidu   ModelAPIConfig `json:"baidu"`
 | 
			
		||||
	XunFei  ModelAPIConfig `json:"xun_fei"`
 | 
			
		||||
 | 
			
		||||
	EnableContext bool `json:"enable_context"` // 是否开启聊天上下文
 | 
			
		||||
	EnableHistory bool `json:"enable_history"` // 是否允许保存聊天记录
 | 
			
		||||
	ContextDeep   int  `json:"context_deep"`   // 上下文深度
 | 
			
		||||
	DallImgNum    int  `json:"dall_img_num"`   // dall-e3 出图数量
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Platform string
 | 
			
		||||
 | 
			
		||||
const OpenAI = Platform("OpenAI")
 | 
			
		||||
@@ -144,16 +130,6 @@ const Baidu = Platform("Baidu")
 | 
			
		||||
const XunFei = Platform("XunFei")
 | 
			
		||||
const QWen = Platform("QWen")
 | 
			
		||||
 | 
			
		||||
// UserChatConfig 用户的聊天配置
 | 
			
		||||
type UserChatConfig struct {
 | 
			
		||||
	ApiKeys map[Platform]string `json:"api_keys"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ModelAPIConfig struct {
 | 
			
		||||
	Temperature float32 `json:"temperature"`
 | 
			
		||||
	MaxTokens   int     `json:"max_tokens"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type SystemConfig struct {
 | 
			
		||||
	Title      string `json:"title"`
 | 
			
		||||
	AdminTitle string `json:"admin_title"`
 | 
			
		||||
@@ -178,4 +154,7 @@ type SystemConfig struct {
 | 
			
		||||
	DallPower int `json:"dall_power"` // DALLE3 绘图消耗算力
 | 
			
		||||
 | 
			
		||||
	WechatCardURL string `json:"wechat_card_url"` // 微信客服地址
 | 
			
		||||
 | 
			
		||||
	EnableContext bool `json:"enable_context"`
 | 
			
		||||
	ContextDeep   int  `json:"context_deep"`
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -32,7 +32,7 @@ func (h *ApiKeyHandler) Save(c *gin.Context) {
 | 
			
		||||
		Value    string `json:"value"`
 | 
			
		||||
		ApiURL   string `json:"api_url"`
 | 
			
		||||
		Enabled  bool   `json:"enabled"`
 | 
			
		||||
		UseProxy bool   `json:"use_proxy"`
 | 
			
		||||
		ProxyURL string `json:"proxy_url"`
 | 
			
		||||
	}
 | 
			
		||||
	if err := c.ShouldBindJSON(&data); err != nil {
 | 
			
		||||
		resp.ERROR(c, types.InvalidArgs)
 | 
			
		||||
@@ -48,7 +48,7 @@ func (h *ApiKeyHandler) Save(c *gin.Context) {
 | 
			
		||||
	apiKey.Type = data.Type
 | 
			
		||||
	apiKey.ApiURL = data.ApiURL
 | 
			
		||||
	apiKey.Enabled = data.Enabled
 | 
			
		||||
	apiKey.UseProxy = data.UseProxy
 | 
			
		||||
	apiKey.ProxyURL = data.ProxyURL
 | 
			
		||||
	apiKey.Name = data.Name
 | 
			
		||||
	res := h.db.Save(&apiKey)
 | 
			
		||||
	if res.Error != nil {
 | 
			
		||||
 
 | 
			
		||||
@@ -24,14 +24,15 @@ func NewChatHandler(app *core.AppServer, db *gorm.DB) *ChatHandler {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type chatItemVo struct {
 | 
			
		||||
	Username  string `json:"username"`
 | 
			
		||||
	UserId    uint   `json:"user_id"`
 | 
			
		||||
	ChatId    string `json:"chat_id"`
 | 
			
		||||
	Title     string `json:"title"`
 | 
			
		||||
	Model     string `json:"model"`
 | 
			
		||||
	Token     int    `json:"token"`
 | 
			
		||||
	CreatedAt int64  `json:"created_at"`
 | 
			
		||||
	MsgNum    int    `json:"msg_num"` // 消息数量
 | 
			
		||||
	Username  string      `json:"username"`
 | 
			
		||||
	UserId    uint        `json:"user_id"`
 | 
			
		||||
	ChatId    string      `json:"chat_id"`
 | 
			
		||||
	Title     string      `json:"title"`
 | 
			
		||||
	Role      vo.ChatRole `json:"role"`
 | 
			
		||||
	Model     string      `json:"model"`
 | 
			
		||||
	Token     int         `json:"token"`
 | 
			
		||||
	CreatedAt int64       `json:"created_at"`
 | 
			
		||||
	MsgNum    int         `json:"msg_num"` // 消息数量
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *ChatHandler) List(c *gin.Context) {
 | 
			
		||||
@@ -78,18 +79,23 @@ func (h *ChatHandler) List(c *gin.Context) {
 | 
			
		||||
	if res.Error == nil {
 | 
			
		||||
		userIds := make([]uint, 0)
 | 
			
		||||
		chatIds := make([]string, 0)
 | 
			
		||||
		roleIds := make([]uint, 0)
 | 
			
		||||
		for _, item := range items {
 | 
			
		||||
			userIds = append(userIds, item.UserId)
 | 
			
		||||
			chatIds = append(chatIds, item.ChatId)
 | 
			
		||||
			roleIds = append(roleIds, item.RoleId)
 | 
			
		||||
		}
 | 
			
		||||
		var messages []model.ChatMessage
 | 
			
		||||
		var users []model.User
 | 
			
		||||
		var roles []model.ChatRole
 | 
			
		||||
		h.db.Where("chat_id IN ?", chatIds).Find(&messages)
 | 
			
		||||
		h.db.Where("id IN ?", userIds).Find(&users)
 | 
			
		||||
		h.db.Where("id IN ?", roleIds).Find(&roles)
 | 
			
		||||
 | 
			
		||||
		tokenMap := make(map[string]int)
 | 
			
		||||
		userMap := make(map[uint]string)
 | 
			
		||||
		msgMap := make(map[string]int)
 | 
			
		||||
		roleMap := make(map[uint]vo.ChatRole)
 | 
			
		||||
		for _, msg := range messages {
 | 
			
		||||
			tokenMap[msg.ChatId] += msg.Tokens
 | 
			
		||||
			msgMap[msg.ChatId] += 1
 | 
			
		||||
@@ -97,6 +103,14 @@ func (h *ChatHandler) List(c *gin.Context) {
 | 
			
		||||
		for _, user := range users {
 | 
			
		||||
			userMap[user.Id] = user.Username
 | 
			
		||||
		}
 | 
			
		||||
		for _, r := range roles {
 | 
			
		||||
			var roleVo vo.ChatRole
 | 
			
		||||
			err := utils.CopyObject(r, &roleVo)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			roleMap[r.Id] = roleVo
 | 
			
		||||
		}
 | 
			
		||||
		for _, item := range items {
 | 
			
		||||
			list = append(list, chatItemVo{
 | 
			
		||||
				UserId:    item.UserId,
 | 
			
		||||
@@ -106,6 +120,7 @@ func (h *ChatHandler) List(c *gin.Context) {
 | 
			
		||||
				Model:     item.Model,
 | 
			
		||||
				Token:     tokenMap[item.ChatId],
 | 
			
		||||
				MsgNum:    msgMap[item.ChatId],
 | 
			
		||||
				Role:      roleMap[item.RoleId],
 | 
			
		||||
				CreatedAt: item.CreatedAt.Unix(),
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -26,15 +26,18 @@ func NewChatModelHandler(app *core.AppServer, db *gorm.DB) *ChatModelHandler {
 | 
			
		||||
 | 
			
		||||
func (h *ChatModelHandler) Save(c *gin.Context) {
 | 
			
		||||
	var data struct {
 | 
			
		||||
		Id        uint   `json:"id"`
 | 
			
		||||
		Name      string `json:"name"`
 | 
			
		||||
		Value     string `json:"value"`
 | 
			
		||||
		Enabled   bool   `json:"enabled"`
 | 
			
		||||
		SortNum   int    `json:"sort_num"`
 | 
			
		||||
		Open      bool   `json:"open"`
 | 
			
		||||
		Platform  string `json:"platform"`
 | 
			
		||||
		Weight    int    `json:"weight"`
 | 
			
		||||
		CreatedAt int64  `json:"created_at"`
 | 
			
		||||
		Id          uint   `json:"id"`
 | 
			
		||||
		Name        string `json:"name"`
 | 
			
		||||
		Value       string `json:"value"`
 | 
			
		||||
		Enabled     bool   `json:"enabled"`
 | 
			
		||||
		SortNum     int    `json:"sort_num"`
 | 
			
		||||
		Open        bool   `json:"open"`
 | 
			
		||||
		Platform    string `json:"platform"`
 | 
			
		||||
		Power       int    `json:"power"`
 | 
			
		||||
		MaxTokens   int    `json:"max_tokens"`  // 最大响应长度
 | 
			
		||||
		MaxContext  int    `json:"max_context"` // 最大上下文长度
 | 
			
		||||
		Temperature string `json:"temperature"` // 模型温度
 | 
			
		||||
		CreatedAt   int64  `json:"created_at"`
 | 
			
		||||
	}
 | 
			
		||||
	if err := c.ShouldBindJSON(&data); err != nil {
 | 
			
		||||
		resp.ERROR(c, types.InvalidArgs)
 | 
			
		||||
@@ -42,13 +45,16 @@ func (h *ChatModelHandler) Save(c *gin.Context) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	item := model.ChatModel{
 | 
			
		||||
		Platform: data.Platform,
 | 
			
		||||
		Name:     data.Name,
 | 
			
		||||
		Value:    data.Value,
 | 
			
		||||
		Enabled:  data.Enabled,
 | 
			
		||||
		SortNum:  data.SortNum,
 | 
			
		||||
		Open:     data.Open,
 | 
			
		||||
		Power:    data.Weight}
 | 
			
		||||
		Platform:    data.Platform,
 | 
			
		||||
		Name:        data.Name,
 | 
			
		||||
		Value:       data.Value,
 | 
			
		||||
		Enabled:     data.Enabled,
 | 
			
		||||
		SortNum:     data.SortNum,
 | 
			
		||||
		Open:        data.Open,
 | 
			
		||||
		MaxTokens:   data.MaxTokens,
 | 
			
		||||
		MaxContext:  data.MaxContext,
 | 
			
		||||
		Temperature: float32(utils.Str2Float(data.Temperature)),
 | 
			
		||||
		Power:       data.Power}
 | 
			
		||||
	item.Id = data.Id
 | 
			
		||||
	if item.Id > 0 {
 | 
			
		||||
		item.CreatedAt = time.Unix(data.CreatedAt, 0)
 | 
			
		||||
@@ -145,19 +151,16 @@ func (h *ChatModelHandler) Sort(c *gin.Context) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *ChatModelHandler) Remove(c *gin.Context) {
 | 
			
		||||
	var data struct {
 | 
			
		||||
		Id uint
 | 
			
		||||
	}
 | 
			
		||||
	if err := c.ShouldBindJSON(&data); err != nil {
 | 
			
		||||
	id := h.GetInt(c, "id", 0)
 | 
			
		||||
	if id <= 0 {
 | 
			
		||||
		resp.ERROR(c, types.InvalidArgs)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if data.Id > 0 {
 | 
			
		||||
		res := h.db.Where("id = ?", data.Id).Delete(&model.ChatModel{})
 | 
			
		||||
		if res.Error != nil {
 | 
			
		||||
			resp.ERROR(c, "更新数据库失败!")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	res := h.db.Where("id = ?", id).Delete(&model.ChatModel{})
 | 
			
		||||
	if res.Error != nil {
 | 
			
		||||
		resp.ERROR(c, "更新数据库失败!")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	resp.SUCCESS(c)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -56,8 +56,6 @@ func (h *ConfigHandler) Update(c *gin.Context) {
 | 
			
		||||
		var err error
 | 
			
		||||
		if data.Key == "system" {
 | 
			
		||||
			err = utils.JsonDecode(cfg.Config, &h.App.SysConfig)
 | 
			
		||||
		} else if data.Key == "chat" {
 | 
			
		||||
			err = utils.JsonDecode(cfg.Config, &h.App.ChatConfig)
 | 
			
		||||
		}
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			resp.ERROR(c, "Failed to update config cache: "+err.Error())
 | 
			
		||||
 
 | 
			
		||||
@@ -107,13 +107,6 @@ func (h *UserHandler) Save(c *gin.Context) {
 | 
			
		||||
			ChatRoles:   utils.JsonEncode(data.ChatRoles),
 | 
			
		||||
			ChatModels:  utils.JsonEncode(data.ChatModels),
 | 
			
		||||
			ExpiredTime: utils.Str2stamp(data.ExpiredTime),
 | 
			
		||||
			ChatConfig: utils.JsonEncode(types.UserChatConfig{
 | 
			
		||||
				ApiKeys: map[types.Platform]string{
 | 
			
		||||
					types.OpenAI:  "",
 | 
			
		||||
					types.Azure:   "",
 | 
			
		||||
					types.ChatGLM: "",
 | 
			
		||||
				},
 | 
			
		||||
			}),
 | 
			
		||||
		}
 | 
			
		||||
		res = h.db.Create(&u)
 | 
			
		||||
		_ = utils.CopyObject(u, &userVo)
 | 
			
		||||
 
 | 
			
		||||
@@ -111,66 +111,64 @@ func (h *ChatHandler) sendAzureMessage(
 | 
			
		||||
			useMsg := types.Message{Role: "user", Content: prompt}
 | 
			
		||||
 | 
			
		||||
			// 更新上下文消息,如果是调用函数则不需要更新上下文
 | 
			
		||||
			if h.App.ChatConfig.EnableContext {
 | 
			
		||||
			if h.App.SysConfig.EnableContext {
 | 
			
		||||
				chatCtx = append(chatCtx, useMsg)  // 提问消息
 | 
			
		||||
				chatCtx = append(chatCtx, message) // 回复消息
 | 
			
		||||
				h.App.ChatContexts.Put(session.ChatId, chatCtx)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// 追加聊天记录
 | 
			
		||||
			if h.App.ChatConfig.EnableHistory {
 | 
			
		||||
				// for prompt
 | 
			
		||||
				promptToken, err := utils.CalcTokens(prompt, req.Model)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					logger.Error(err)
 | 
			
		||||
				}
 | 
			
		||||
				historyUserMsg := model.ChatMessage{
 | 
			
		||||
					UserId:     userVo.Id,
 | 
			
		||||
					ChatId:     session.ChatId,
 | 
			
		||||
					RoleId:     role.Id,
 | 
			
		||||
					Type:       types.PromptMsg,
 | 
			
		||||
					Icon:       userVo.Avatar,
 | 
			
		||||
					Content:    template.HTMLEscapeString(prompt),
 | 
			
		||||
					Tokens:     promptToken,
 | 
			
		||||
					UseContext: true,
 | 
			
		||||
					Model:      req.Model,
 | 
			
		||||
				}
 | 
			
		||||
				historyUserMsg.CreatedAt = promptCreatedAt
 | 
			
		||||
				historyUserMsg.UpdatedAt = promptCreatedAt
 | 
			
		||||
				res := h.db.Save(&historyUserMsg)
 | 
			
		||||
				if res.Error != nil {
 | 
			
		||||
					logger.Error("failed to save prompt history message: ", res.Error)
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// 计算本次对话消耗的总 token 数量
 | 
			
		||||
				replyTokens, _ := utils.CalcTokens(message.Content, req.Model)
 | 
			
		||||
				replyTokens += getTotalTokens(req)
 | 
			
		||||
 | 
			
		||||
				historyReplyMsg := model.ChatMessage{
 | 
			
		||||
					UserId:     userVo.Id,
 | 
			
		||||
					ChatId:     session.ChatId,
 | 
			
		||||
					RoleId:     role.Id,
 | 
			
		||||
					Type:       types.ReplyMsg,
 | 
			
		||||
					Icon:       role.Icon,
 | 
			
		||||
					Content:    message.Content,
 | 
			
		||||
					Tokens:     replyTokens,
 | 
			
		||||
					UseContext: true,
 | 
			
		||||
					Model:      req.Model,
 | 
			
		||||
				}
 | 
			
		||||
				historyReplyMsg.CreatedAt = replyCreatedAt
 | 
			
		||||
				historyReplyMsg.UpdatedAt = replyCreatedAt
 | 
			
		||||
				res = h.db.Create(&historyReplyMsg)
 | 
			
		||||
				if res.Error != nil {
 | 
			
		||||
					logger.Error("failed to save reply history message: ", res.Error)
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// 更新用户算力
 | 
			
		||||
				h.subUserPower(userVo, session, promptToken, replyTokens)
 | 
			
		||||
			// for prompt
 | 
			
		||||
			promptToken, err := utils.CalcTokens(prompt, req.Model)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logger.Error(err)
 | 
			
		||||
			}
 | 
			
		||||
			historyUserMsg := model.ChatMessage{
 | 
			
		||||
				UserId:     userVo.Id,
 | 
			
		||||
				ChatId:     session.ChatId,
 | 
			
		||||
				RoleId:     role.Id,
 | 
			
		||||
				Type:       types.PromptMsg,
 | 
			
		||||
				Icon:       userVo.Avatar,
 | 
			
		||||
				Content:    template.HTMLEscapeString(prompt),
 | 
			
		||||
				Tokens:     promptToken,
 | 
			
		||||
				UseContext: true,
 | 
			
		||||
				Model:      req.Model,
 | 
			
		||||
			}
 | 
			
		||||
			historyUserMsg.CreatedAt = promptCreatedAt
 | 
			
		||||
			historyUserMsg.UpdatedAt = promptCreatedAt
 | 
			
		||||
			res := h.db.Save(&historyUserMsg)
 | 
			
		||||
			if res.Error != nil {
 | 
			
		||||
				logger.Error("failed to save prompt history message: ", res.Error)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// 计算本次对话消耗的总 token 数量
 | 
			
		||||
			replyTokens, _ := utils.CalcTokens(message.Content, req.Model)
 | 
			
		||||
			replyTokens += getTotalTokens(req)
 | 
			
		||||
 | 
			
		||||
			historyReplyMsg := model.ChatMessage{
 | 
			
		||||
				UserId:     userVo.Id,
 | 
			
		||||
				ChatId:     session.ChatId,
 | 
			
		||||
				RoleId:     role.Id,
 | 
			
		||||
				Type:       types.ReplyMsg,
 | 
			
		||||
				Icon:       role.Icon,
 | 
			
		||||
				Content:    message.Content,
 | 
			
		||||
				Tokens:     replyTokens,
 | 
			
		||||
				UseContext: true,
 | 
			
		||||
				Model:      req.Model,
 | 
			
		||||
			}
 | 
			
		||||
			historyReplyMsg.CreatedAt = replyCreatedAt
 | 
			
		||||
			historyReplyMsg.UpdatedAt = replyCreatedAt
 | 
			
		||||
			res = h.db.Create(&historyReplyMsg)
 | 
			
		||||
			if res.Error != nil {
 | 
			
		||||
				logger.Error("failed to save reply history message: ", res.Error)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// 更新用户算力
 | 
			
		||||
			h.subUserPower(userVo, session, promptToken, replyTokens)
 | 
			
		||||
 | 
			
		||||
			// 保存当前会话
 | 
			
		||||
			var chatItem model.ChatItem
 | 
			
		||||
			res := h.db.Where("chat_id = ?", session.ChatId).First(&chatItem)
 | 
			
		||||
			res = h.db.Where("chat_id = ?", session.ChatId).First(&chatItem)
 | 
			
		||||
			if res.Error != nil {
 | 
			
		||||
				chatItem.ChatId = session.ChatId
 | 
			
		||||
				chatItem.UserId = session.UserId
 | 
			
		||||
 
 | 
			
		||||
@@ -135,65 +135,63 @@ func (h *ChatHandler) sendBaiduMessage(
 | 
			
		||||
			useMsg := types.Message{Role: "user", Content: prompt}
 | 
			
		||||
 | 
			
		||||
			// 更新上下文消息,如果是调用函数则不需要更新上下文
 | 
			
		||||
			if h.App.ChatConfig.EnableContext {
 | 
			
		||||
			if h.App.SysConfig.EnableContext {
 | 
			
		||||
				chatCtx = append(chatCtx, useMsg)  // 提问消息
 | 
			
		||||
				chatCtx = append(chatCtx, message) // 回复消息
 | 
			
		||||
				h.App.ChatContexts.Put(session.ChatId, chatCtx)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// 追加聊天记录
 | 
			
		||||
			if h.App.ChatConfig.EnableHistory {
 | 
			
		||||
				// for prompt
 | 
			
		||||
				promptToken, err := utils.CalcTokens(prompt, req.Model)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					logger.Error(err)
 | 
			
		||||
				}
 | 
			
		||||
				historyUserMsg := model.ChatMessage{
 | 
			
		||||
					UserId:     userVo.Id,
 | 
			
		||||
					ChatId:     session.ChatId,
 | 
			
		||||
					RoleId:     role.Id,
 | 
			
		||||
					Type:       types.PromptMsg,
 | 
			
		||||
					Icon:       userVo.Avatar,
 | 
			
		||||
					Content:    template.HTMLEscapeString(prompt),
 | 
			
		||||
					Tokens:     promptToken,
 | 
			
		||||
					UseContext: true,
 | 
			
		||||
					Model:      req.Model,
 | 
			
		||||
				}
 | 
			
		||||
				historyUserMsg.CreatedAt = promptCreatedAt
 | 
			
		||||
				historyUserMsg.UpdatedAt = promptCreatedAt
 | 
			
		||||
				res := h.db.Save(&historyUserMsg)
 | 
			
		||||
				if res.Error != nil {
 | 
			
		||||
					logger.Error("failed to save prompt history message: ", res.Error)
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// for reply
 | 
			
		||||
				// 计算本次对话消耗的总 token 数量
 | 
			
		||||
				replyTokens, _ := utils.CalcTokens(message.Content, req.Model)
 | 
			
		||||
				totalTokens := replyTokens + getTotalTokens(req)
 | 
			
		||||
				historyReplyMsg := model.ChatMessage{
 | 
			
		||||
					UserId:     userVo.Id,
 | 
			
		||||
					ChatId:     session.ChatId,
 | 
			
		||||
					RoleId:     role.Id,
 | 
			
		||||
					Type:       types.ReplyMsg,
 | 
			
		||||
					Icon:       role.Icon,
 | 
			
		||||
					Content:    message.Content,
 | 
			
		||||
					Tokens:     totalTokens,
 | 
			
		||||
					UseContext: true,
 | 
			
		||||
					Model:      req.Model,
 | 
			
		||||
				}
 | 
			
		||||
				historyReplyMsg.CreatedAt = replyCreatedAt
 | 
			
		||||
				historyReplyMsg.UpdatedAt = replyCreatedAt
 | 
			
		||||
				res = h.db.Create(&historyReplyMsg)
 | 
			
		||||
				if res.Error != nil {
 | 
			
		||||
					logger.Error("failed to save reply history message: ", res.Error)
 | 
			
		||||
				}
 | 
			
		||||
				// 更新用户算力
 | 
			
		||||
				h.subUserPower(userVo, session, promptToken, replyTokens)
 | 
			
		||||
			// for prompt
 | 
			
		||||
			promptToken, err := utils.CalcTokens(prompt, req.Model)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logger.Error(err)
 | 
			
		||||
			}
 | 
			
		||||
			historyUserMsg := model.ChatMessage{
 | 
			
		||||
				UserId:     userVo.Id,
 | 
			
		||||
				ChatId:     session.ChatId,
 | 
			
		||||
				RoleId:     role.Id,
 | 
			
		||||
				Type:       types.PromptMsg,
 | 
			
		||||
				Icon:       userVo.Avatar,
 | 
			
		||||
				Content:    template.HTMLEscapeString(prompt),
 | 
			
		||||
				Tokens:     promptToken,
 | 
			
		||||
				UseContext: true,
 | 
			
		||||
				Model:      req.Model,
 | 
			
		||||
			}
 | 
			
		||||
			historyUserMsg.CreatedAt = promptCreatedAt
 | 
			
		||||
			historyUserMsg.UpdatedAt = promptCreatedAt
 | 
			
		||||
			res := h.db.Save(&historyUserMsg)
 | 
			
		||||
			if res.Error != nil {
 | 
			
		||||
				logger.Error("failed to save prompt history message: ", res.Error)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// for reply
 | 
			
		||||
			// 计算本次对话消耗的总 token 数量
 | 
			
		||||
			replyTokens, _ := utils.CalcTokens(message.Content, req.Model)
 | 
			
		||||
			totalTokens := replyTokens + getTotalTokens(req)
 | 
			
		||||
			historyReplyMsg := model.ChatMessage{
 | 
			
		||||
				UserId:     userVo.Id,
 | 
			
		||||
				ChatId:     session.ChatId,
 | 
			
		||||
				RoleId:     role.Id,
 | 
			
		||||
				Type:       types.ReplyMsg,
 | 
			
		||||
				Icon:       role.Icon,
 | 
			
		||||
				Content:    message.Content,
 | 
			
		||||
				Tokens:     totalTokens,
 | 
			
		||||
				UseContext: true,
 | 
			
		||||
				Model:      req.Model,
 | 
			
		||||
			}
 | 
			
		||||
			historyReplyMsg.CreatedAt = replyCreatedAt
 | 
			
		||||
			historyReplyMsg.UpdatedAt = replyCreatedAt
 | 
			
		||||
			res = h.db.Create(&historyReplyMsg)
 | 
			
		||||
			if res.Error != nil {
 | 
			
		||||
				logger.Error("failed to save reply history message: ", res.Error)
 | 
			
		||||
			}
 | 
			
		||||
			// 更新用户算力
 | 
			
		||||
			h.subUserPower(userVo, session, promptToken, replyTokens)
 | 
			
		||||
 | 
			
		||||
			// 保存当前会话
 | 
			
		||||
			var chatItem model.ChatItem
 | 
			
		||||
			res := h.db.Where("chat_id = ?", session.ChatId).First(&chatItem)
 | 
			
		||||
			res = h.db.Where("chat_id = ?", session.ChatId).First(&chatItem)
 | 
			
		||||
			if res.Error != nil {
 | 
			
		||||
				chatItem.ChatId = session.ChatId
 | 
			
		||||
				chatItem.UserId = session.UserId
 | 
			
		||||
 
 | 
			
		||||
@@ -57,8 +57,6 @@ func (h *ChatHandler) Init() {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var chatConfig types.ChatConfig
 | 
			
		||||
 | 
			
		||||
// ChatHandle 处理聊天 WebSocket 请求
 | 
			
		||||
func (h *ChatHandler) ChatHandle(c *gin.Context) {
 | 
			
		||||
	ws, err := (&websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }}).Upgrade(c.Writer, c.Request, nil)
 | 
			
		||||
@@ -109,10 +107,13 @@ func (h *ChatHandler) ChatHandle(c *gin.Context) {
 | 
			
		||||
 | 
			
		||||
	session.ChatId = chatId
 | 
			
		||||
	session.Model = types.ChatModel{
 | 
			
		||||
		Id:       chatModel.Id,
 | 
			
		||||
		Value:    chatModel.Value,
 | 
			
		||||
		Power:    chatModel.Power,
 | 
			
		||||
		Platform: types.Platform(chatModel.Platform)}
 | 
			
		||||
		Id:          chatModel.Id,
 | 
			
		||||
		Value:       chatModel.Value,
 | 
			
		||||
		Power:       chatModel.Power,
 | 
			
		||||
		MaxTokens:   chatModel.MaxTokens,
 | 
			
		||||
		MaxContext:  chatModel.MaxContext,
 | 
			
		||||
		Temperature: chatModel.Temperature,
 | 
			
		||||
		Platform:    types.Platform(chatModel.Platform)}
 | 
			
		||||
	logger.Infof("New websocket connected, IP: %s, Username: %s", c.ClientIP(), session.Username)
 | 
			
		||||
	var chatRole model.ChatRole
 | 
			
		||||
	res = h.db.First(&chatRole, roleId)
 | 
			
		||||
@@ -122,15 +123,6 @@ func (h *ChatHandler) ChatHandle(c *gin.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 初始化聊天配置
 | 
			
		||||
	var config model.Config
 | 
			
		||||
	h.db.Where("marker", "chat").First(&config)
 | 
			
		||||
	err = utils.JsonDecode(config.Config, &chatConfig)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		utils.ReplyMessage(client, "加载系统配置失败,连接已关闭!!!")
 | 
			
		||||
		c.Abort()
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	h.Init()
 | 
			
		||||
 | 
			
		||||
	// 保存会话连接
 | 
			
		||||
@@ -213,7 +205,7 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if userVo.Power <= 0 && userVo.ChatConfig.ApiKeys[session.Model.Platform] == "" {
 | 
			
		||||
	if userVo.Power <= 0 {
 | 
			
		||||
		utils.ReplyMessage(ws, "您的对话次数已经用尽,请联系管理员或者充值点卡继续对话!")
 | 
			
		||||
		utils.ReplyMessage(ws, ErrImg)
 | 
			
		||||
		return nil
 | 
			
		||||
@@ -227,7 +219,7 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
 | 
			
		||||
 | 
			
		||||
	// 检查 prompt 长度是否超过了当前模型允许的最大上下文长度
 | 
			
		||||
	promptTokens, err := utils.CalcTokens(prompt, session.Model.Value)
 | 
			
		||||
	if promptTokens > types.GetModelMaxToken(session.Model.Value) {
 | 
			
		||||
	if promptTokens > session.Model.MaxContext {
 | 
			
		||||
		utils.ReplyMessage(ws, "对话内容超出了当前模型允许的最大上下文长度!")
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
@@ -237,21 +229,13 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
 | 
			
		||||
		Stream: true,
 | 
			
		||||
	}
 | 
			
		||||
	switch session.Model.Platform {
 | 
			
		||||
	case types.Azure:
 | 
			
		||||
		req.Temperature = h.App.ChatConfig.Azure.Temperature
 | 
			
		||||
		req.MaxTokens = h.App.ChatConfig.Azure.MaxTokens
 | 
			
		||||
		break
 | 
			
		||||
	case types.ChatGLM:
 | 
			
		||||
		req.Temperature = h.App.ChatConfig.ChatGML.Temperature
 | 
			
		||||
		req.MaxTokens = h.App.ChatConfig.ChatGML.MaxTokens
 | 
			
		||||
		break
 | 
			
		||||
	case types.Baidu:
 | 
			
		||||
		req.Temperature = h.App.ChatConfig.OpenAI.Temperature
 | 
			
		||||
		// TODO: 目前只支持 ERNIE-Bot-turbo 模型,如果是 ERNIE-Bot 模型则需要增加函数支持
 | 
			
		||||
	case types.Azure, types.ChatGLM, types.Baidu, types.XunFei:
 | 
			
		||||
		req.Temperature = session.Model.Temperature
 | 
			
		||||
		req.MaxTokens = session.Model.MaxTokens
 | 
			
		||||
		break
 | 
			
		||||
	case types.OpenAI:
 | 
			
		||||
		req.Temperature = h.App.ChatConfig.OpenAI.Temperature
 | 
			
		||||
		req.MaxTokens = h.App.ChatConfig.OpenAI.MaxTokens
 | 
			
		||||
		req.Temperature = session.Model.Temperature
 | 
			
		||||
		req.MaxTokens = session.Model.MaxTokens
 | 
			
		||||
		// OpenAI 支持函数功能
 | 
			
		||||
		var items []model.Function
 | 
			
		||||
		res := h.db.Where("enabled", true).Find(&items)
 | 
			
		||||
@@ -283,15 +267,13 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
 | 
			
		||||
			req.Tools = tools
 | 
			
		||||
			req.ToolChoice = "auto"
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	case types.XunFei:
 | 
			
		||||
		req.Temperature = h.App.ChatConfig.XunFei.Temperature
 | 
			
		||||
		req.MaxTokens = h.App.ChatConfig.XunFei.MaxTokens
 | 
			
		||||
		break
 | 
			
		||||
	case types.QWen:
 | 
			
		||||
		req.Input = map[string]interface{}{"messages": []map[string]string{{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}}}
 | 
			
		||||
		req.Parameters = map[string]interface{}{}
 | 
			
		||||
		req.Parameters = map[string]interface{}{
 | 
			
		||||
			"max_tokens":  session.Model.MaxTokens,
 | 
			
		||||
			"temperature": session.Model.Temperature,
 | 
			
		||||
		}
 | 
			
		||||
		break
 | 
			
		||||
 | 
			
		||||
	default:
 | 
			
		||||
		utils.ReplyMessage(ws, "不支持的平台:"+session.Model.Platform+",请联系管理员!")
 | 
			
		||||
		utils.ReplyMessage(ws, ErrImg)
 | 
			
		||||
@@ -301,14 +283,14 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
 | 
			
		||||
	// 加载聊天上下文
 | 
			
		||||
	chatCtx := make([]types.Message, 0)
 | 
			
		||||
	messages := make([]types.Message, 0)
 | 
			
		||||
	if h.App.ChatConfig.EnableContext {
 | 
			
		||||
	if h.App.SysConfig.EnableContext {
 | 
			
		||||
		if h.App.ChatContexts.Has(session.ChatId) {
 | 
			
		||||
			messages = h.App.ChatContexts.Get(session.ChatId)
 | 
			
		||||
		} else {
 | 
			
		||||
			_ = utils.JsonDecode(role.Context, &messages)
 | 
			
		||||
			if chatConfig.ContextDeep > 0 {
 | 
			
		||||
			if h.App.SysConfig.ContextDeep > 0 {
 | 
			
		||||
				var historyMessages []model.ChatMessage
 | 
			
		||||
				res := h.db.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(h.App.SysConfig.ContextDeep).Order("id DESC").Find(&historyMessages)
 | 
			
		||||
				if res.Error == nil {
 | 
			
		||||
					for i := len(historyMessages) - 1; i >= 0; i-- {
 | 
			
		||||
						msg := historyMessages[i]
 | 
			
		||||
@@ -331,12 +313,12 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
 | 
			
		||||
		for _, v := range messages {
 | 
			
		||||
			tks, _ := utils.CalcTokens(v.Content, req.Model)
 | 
			
		||||
			// 上下文 token 超出了模型的最大上下文长度
 | 
			
		||||
			if tokens+tks >= types.GetModelMaxToken(req.Model) {
 | 
			
		||||
			if tokens+tks >= session.Model.MaxContext {
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// 上下文的深度超出了模型的最大上下文深度
 | 
			
		||||
			if len(chatCtx) >= h.App.ChatConfig.ContextDeep {
 | 
			
		||||
			if len(chatCtx) >= h.App.SysConfig.ContextDeep {
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
@@ -351,10 +333,17 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
 | 
			
		||||
		reqMgs = append(reqMgs, m)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	req.Messages = append(reqMgs, map[string]interface{}{
 | 
			
		||||
		"role":    "user",
 | 
			
		||||
		"content": prompt,
 | 
			
		||||
	})
 | 
			
		||||
	if session.Model.Platform == types.QWen {
 | 
			
		||||
		req.Input = map[string]interface{}{"prompt": prompt}
 | 
			
		||||
		if len(reqMgs) > 0 {
 | 
			
		||||
			req.Input["messages"] = reqMgs
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		req.Messages = append(reqMgs, map[string]interface{}{
 | 
			
		||||
			"role":    "user",
 | 
			
		||||
			"content": prompt,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch session.Model.Platform {
 | 
			
		||||
	case types.Azure:
 | 
			
		||||
@@ -497,9 +486,8 @@ func (h *ChatHandler) doRequest(ctx context.Context, req types.ApiRequest, platf
 | 
			
		||||
	request = request.WithContext(ctx)
 | 
			
		||||
	request.Header.Set("Content-Type", "application/json")
 | 
			
		||||
	var proxyURL string
 | 
			
		||||
	if h.App.Config.ProxyURL != "" && apiKey.UseProxy { // 使用代理
 | 
			
		||||
		proxyURL = h.App.Config.ProxyURL
 | 
			
		||||
		proxy, _ := url.Parse(proxyURL)
 | 
			
		||||
	if apiKey.ProxyURL != "" { // 使用代理
 | 
			
		||||
		proxy, _ := url.Parse(apiKey.ProxyURL)
 | 
			
		||||
		client = &http.Client{
 | 
			
		||||
			Transport: &http.Transport{
 | 
			
		||||
				Proxy: http.ProxyURL(proxy),
 | 
			
		||||
@@ -542,7 +530,7 @@ func (h *ChatHandler) subUserPower(userVo vo.User, session *types.ChatSession, p
 | 
			
		||||
	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{
 | 
			
		||||
		h.db.Create(&model.PowerLog{
 | 
			
		||||
			UserId:    userVo.Id,
 | 
			
		||||
			Username:  userVo.Username,
 | 
			
		||||
			Type:      types.PowerConsume,
 | 
			
		||||
 
 | 
			
		||||
@@ -126,7 +126,7 @@ func (h *ChatHandler) History(c *gin.Context) {
 | 
			
		||||
	chatId := c.Query("chat_id") // 会话 ID
 | 
			
		||||
	var items []model.ChatMessage
 | 
			
		||||
	var messages = make([]vo.HistoryMessage, 0)
 | 
			
		||||
	res := h.db.Debug().Where("chat_id = ?", chatId).Find(&items)
 | 
			
		||||
	res := h.db.Where("chat_id = ?", chatId).Find(&items)
 | 
			
		||||
	if res.Error != nil {
 | 
			
		||||
		resp.ERROR(c, "No history message")
 | 
			
		||||
		return
 | 
			
		||||
 
 | 
			
		||||
@@ -114,66 +114,64 @@ func (h *ChatHandler) sendChatGLMMessage(
 | 
			
		||||
			useMsg := types.Message{Role: "user", Content: prompt}
 | 
			
		||||
 | 
			
		||||
			// 更新上下文消息,如果是调用函数则不需要更新上下文
 | 
			
		||||
			if h.App.ChatConfig.EnableContext {
 | 
			
		||||
			if h.App.SysConfig.EnableContext {
 | 
			
		||||
				chatCtx = append(chatCtx, useMsg)  // 提问消息
 | 
			
		||||
				chatCtx = append(chatCtx, message) // 回复消息
 | 
			
		||||
				h.App.ChatContexts.Put(session.ChatId, chatCtx)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// 追加聊天记录
 | 
			
		||||
			if h.App.ChatConfig.EnableHistory {
 | 
			
		||||
				// for prompt
 | 
			
		||||
				promptToken, err := utils.CalcTokens(prompt, req.Model)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					logger.Error(err)
 | 
			
		||||
				}
 | 
			
		||||
				historyUserMsg := model.ChatMessage{
 | 
			
		||||
					UserId:     userVo.Id,
 | 
			
		||||
					ChatId:     session.ChatId,
 | 
			
		||||
					RoleId:     role.Id,
 | 
			
		||||
					Type:       types.PromptMsg,
 | 
			
		||||
					Icon:       userVo.Avatar,
 | 
			
		||||
					Content:    template.HTMLEscapeString(prompt),
 | 
			
		||||
					Tokens:     promptToken,
 | 
			
		||||
					UseContext: true,
 | 
			
		||||
					Model:      req.Model,
 | 
			
		||||
				}
 | 
			
		||||
				historyUserMsg.CreatedAt = promptCreatedAt
 | 
			
		||||
				historyUserMsg.UpdatedAt = promptCreatedAt
 | 
			
		||||
				res := h.db.Save(&historyUserMsg)
 | 
			
		||||
				if res.Error != nil {
 | 
			
		||||
					logger.Error("failed to save prompt history message: ", res.Error)
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// for reply
 | 
			
		||||
				// 计算本次对话消耗的总 token 数量
 | 
			
		||||
				replyTokens, _ := utils.CalcTokens(message.Content, req.Model)
 | 
			
		||||
				totalTokens := replyTokens + getTotalTokens(req)
 | 
			
		||||
				historyReplyMsg := model.ChatMessage{
 | 
			
		||||
					UserId:     userVo.Id,
 | 
			
		||||
					ChatId:     session.ChatId,
 | 
			
		||||
					RoleId:     role.Id,
 | 
			
		||||
					Type:       types.ReplyMsg,
 | 
			
		||||
					Icon:       role.Icon,
 | 
			
		||||
					Content:    message.Content,
 | 
			
		||||
					Tokens:     totalTokens,
 | 
			
		||||
					UseContext: true,
 | 
			
		||||
					Model:      req.Model,
 | 
			
		||||
				}
 | 
			
		||||
				historyReplyMsg.CreatedAt = replyCreatedAt
 | 
			
		||||
				historyReplyMsg.UpdatedAt = replyCreatedAt
 | 
			
		||||
				res = h.db.Create(&historyReplyMsg)
 | 
			
		||||
				if res.Error != nil {
 | 
			
		||||
					logger.Error("failed to save reply history message: ", res.Error)
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// 更新用户算力
 | 
			
		||||
				h.subUserPower(userVo, session, promptToken, replyTokens)
 | 
			
		||||
			// for prompt
 | 
			
		||||
			promptToken, err := utils.CalcTokens(prompt, req.Model)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logger.Error(err)
 | 
			
		||||
			}
 | 
			
		||||
			historyUserMsg := model.ChatMessage{
 | 
			
		||||
				UserId:     userVo.Id,
 | 
			
		||||
				ChatId:     session.ChatId,
 | 
			
		||||
				RoleId:     role.Id,
 | 
			
		||||
				Type:       types.PromptMsg,
 | 
			
		||||
				Icon:       userVo.Avatar,
 | 
			
		||||
				Content:    template.HTMLEscapeString(prompt),
 | 
			
		||||
				Tokens:     promptToken,
 | 
			
		||||
				UseContext: true,
 | 
			
		||||
				Model:      req.Model,
 | 
			
		||||
			}
 | 
			
		||||
			historyUserMsg.CreatedAt = promptCreatedAt
 | 
			
		||||
			historyUserMsg.UpdatedAt = promptCreatedAt
 | 
			
		||||
			res := h.db.Save(&historyUserMsg)
 | 
			
		||||
			if res.Error != nil {
 | 
			
		||||
				logger.Error("failed to save prompt history message: ", res.Error)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// for reply
 | 
			
		||||
			// 计算本次对话消耗的总 token 数量
 | 
			
		||||
			replyTokens, _ := utils.CalcTokens(message.Content, req.Model)
 | 
			
		||||
			totalTokens := replyTokens + getTotalTokens(req)
 | 
			
		||||
			historyReplyMsg := model.ChatMessage{
 | 
			
		||||
				UserId:     userVo.Id,
 | 
			
		||||
				ChatId:     session.ChatId,
 | 
			
		||||
				RoleId:     role.Id,
 | 
			
		||||
				Type:       types.ReplyMsg,
 | 
			
		||||
				Icon:       role.Icon,
 | 
			
		||||
				Content:    message.Content,
 | 
			
		||||
				Tokens:     totalTokens,
 | 
			
		||||
				UseContext: true,
 | 
			
		||||
				Model:      req.Model,
 | 
			
		||||
			}
 | 
			
		||||
			historyReplyMsg.CreatedAt = replyCreatedAt
 | 
			
		||||
			historyReplyMsg.UpdatedAt = replyCreatedAt
 | 
			
		||||
			res = h.db.Create(&historyReplyMsg)
 | 
			
		||||
			if res.Error != nil {
 | 
			
		||||
				logger.Error("failed to save reply history message: ", res.Error)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// 更新用户算力
 | 
			
		||||
			h.subUserPower(userVo, session, promptToken, replyTokens)
 | 
			
		||||
 | 
			
		||||
			// 保存当前会话
 | 
			
		||||
			var chatItem model.ChatItem
 | 
			
		||||
			res := h.db.Where("chat_id = ?", session.ChatId).First(&chatItem)
 | 
			
		||||
			res = h.db.Where("chat_id = ?", session.ChatId).First(&chatItem)
 | 
			
		||||
			if res.Error != nil {
 | 
			
		||||
				chatItem.ChatId = session.ChatId
 | 
			
		||||
				chatItem.UserId = session.UserId
 | 
			
		||||
 
 | 
			
		||||
@@ -180,79 +180,77 @@ func (h *ChatHandler) sendOpenAiMessage(
 | 
			
		||||
			useMsg := types.Message{Role: "user", Content: prompt}
 | 
			
		||||
 | 
			
		||||
			// 更新上下文消息,如果是调用函数则不需要更新上下文
 | 
			
		||||
			if h.App.ChatConfig.EnableContext && toolCall == false {
 | 
			
		||||
			if h.App.SysConfig.EnableContext && toolCall == false {
 | 
			
		||||
				chatCtx = append(chatCtx, useMsg)  // 提问消息
 | 
			
		||||
				chatCtx = append(chatCtx, message) // 回复消息
 | 
			
		||||
				h.App.ChatContexts.Put(session.ChatId, chatCtx)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// 追加聊天记录
 | 
			
		||||
			if h.App.ChatConfig.EnableHistory {
 | 
			
		||||
				useContext := true
 | 
			
		||||
				if toolCall {
 | 
			
		||||
					useContext = false
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// for prompt
 | 
			
		||||
				promptToken, err := utils.CalcTokens(prompt, req.Model)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					logger.Error(err)
 | 
			
		||||
				}
 | 
			
		||||
				historyUserMsg := model.ChatMessage{
 | 
			
		||||
					UserId:     userVo.Id,
 | 
			
		||||
					ChatId:     session.ChatId,
 | 
			
		||||
					RoleId:     role.Id,
 | 
			
		||||
					Type:       types.PromptMsg,
 | 
			
		||||
					Icon:       userVo.Avatar,
 | 
			
		||||
					Content:    template.HTMLEscapeString(prompt),
 | 
			
		||||
					Tokens:     promptToken,
 | 
			
		||||
					UseContext: useContext,
 | 
			
		||||
					Model:      req.Model,
 | 
			
		||||
				}
 | 
			
		||||
				historyUserMsg.CreatedAt = promptCreatedAt
 | 
			
		||||
				historyUserMsg.UpdatedAt = promptCreatedAt
 | 
			
		||||
				res := h.db.Save(&historyUserMsg)
 | 
			
		||||
				if res.Error != nil {
 | 
			
		||||
					logger.Error("failed to save prompt history message: ", res.Error)
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// 计算本次对话消耗的总 token 数量
 | 
			
		||||
				var replyTokens = 0
 | 
			
		||||
				if toolCall { // prompt + 函数名 + 参数 token
 | 
			
		||||
					tokens, _ := utils.CalcTokens(function.Name, req.Model)
 | 
			
		||||
					replyTokens += tokens
 | 
			
		||||
					tokens, _ = utils.CalcTokens(utils.InterfaceToString(arguments), req.Model)
 | 
			
		||||
					replyTokens += tokens
 | 
			
		||||
				} else {
 | 
			
		||||
					replyTokens, _ = utils.CalcTokens(message.Content, req.Model)
 | 
			
		||||
				}
 | 
			
		||||
				replyTokens += getTotalTokens(req)
 | 
			
		||||
 | 
			
		||||
				historyReplyMsg := model.ChatMessage{
 | 
			
		||||
					UserId:     userVo.Id,
 | 
			
		||||
					ChatId:     session.ChatId,
 | 
			
		||||
					RoleId:     role.Id,
 | 
			
		||||
					Type:       types.ReplyMsg,
 | 
			
		||||
					Icon:       role.Icon,
 | 
			
		||||
					Content:    h.extractImgUrl(message.Content),
 | 
			
		||||
					Tokens:     replyTokens,
 | 
			
		||||
					UseContext: useContext,
 | 
			
		||||
					Model:      req.Model,
 | 
			
		||||
				}
 | 
			
		||||
				historyReplyMsg.CreatedAt = replyCreatedAt
 | 
			
		||||
				historyReplyMsg.UpdatedAt = replyCreatedAt
 | 
			
		||||
				res = h.db.Create(&historyReplyMsg)
 | 
			
		||||
				if res.Error != nil {
 | 
			
		||||
					logger.Error("failed to save reply history message: ", res.Error)
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// 更新用户算力
 | 
			
		||||
				h.subUserPower(userVo, session, promptToken, replyTokens)
 | 
			
		||||
			useContext := true
 | 
			
		||||
			if toolCall {
 | 
			
		||||
				useContext = false
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// for prompt
 | 
			
		||||
			promptToken, err := utils.CalcTokens(prompt, req.Model)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logger.Error(err)
 | 
			
		||||
			}
 | 
			
		||||
			historyUserMsg := model.ChatMessage{
 | 
			
		||||
				UserId:     userVo.Id,
 | 
			
		||||
				ChatId:     session.ChatId,
 | 
			
		||||
				RoleId:     role.Id,
 | 
			
		||||
				Type:       types.PromptMsg,
 | 
			
		||||
				Icon:       userVo.Avatar,
 | 
			
		||||
				Content:    template.HTMLEscapeString(prompt),
 | 
			
		||||
				Tokens:     promptToken,
 | 
			
		||||
				UseContext: useContext,
 | 
			
		||||
				Model:      req.Model,
 | 
			
		||||
			}
 | 
			
		||||
			historyUserMsg.CreatedAt = promptCreatedAt
 | 
			
		||||
			historyUserMsg.UpdatedAt = promptCreatedAt
 | 
			
		||||
			res := h.db.Save(&historyUserMsg)
 | 
			
		||||
			if res.Error != nil {
 | 
			
		||||
				logger.Error("failed to save prompt history message: ", res.Error)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// 计算本次对话消耗的总 token 数量
 | 
			
		||||
			var replyTokens = 0
 | 
			
		||||
			if toolCall { // prompt + 函数名 + 参数 token
 | 
			
		||||
				tokens, _ := utils.CalcTokens(function.Name, req.Model)
 | 
			
		||||
				replyTokens += tokens
 | 
			
		||||
				tokens, _ = utils.CalcTokens(utils.InterfaceToString(arguments), req.Model)
 | 
			
		||||
				replyTokens += tokens
 | 
			
		||||
			} else {
 | 
			
		||||
				replyTokens, _ = utils.CalcTokens(message.Content, req.Model)
 | 
			
		||||
			}
 | 
			
		||||
			replyTokens += getTotalTokens(req)
 | 
			
		||||
 | 
			
		||||
			historyReplyMsg := model.ChatMessage{
 | 
			
		||||
				UserId:     userVo.Id,
 | 
			
		||||
				ChatId:     session.ChatId,
 | 
			
		||||
				RoleId:     role.Id,
 | 
			
		||||
				Type:       types.ReplyMsg,
 | 
			
		||||
				Icon:       role.Icon,
 | 
			
		||||
				Content:    h.extractImgUrl(message.Content),
 | 
			
		||||
				Tokens:     replyTokens,
 | 
			
		||||
				UseContext: useContext,
 | 
			
		||||
				Model:      req.Model,
 | 
			
		||||
			}
 | 
			
		||||
			historyReplyMsg.CreatedAt = replyCreatedAt
 | 
			
		||||
			historyReplyMsg.UpdatedAt = replyCreatedAt
 | 
			
		||||
			res = h.db.Create(&historyReplyMsg)
 | 
			
		||||
			if res.Error != nil {
 | 
			
		||||
				logger.Error("failed to save reply history message: ", res.Error)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// 更新用户算力
 | 
			
		||||
			h.subUserPower(userVo, session, promptToken, replyTokens)
 | 
			
		||||
 | 
			
		||||
			// 保存当前会话
 | 
			
		||||
			var chatItem model.ChatItem
 | 
			
		||||
			res := h.db.Where("chat_id = ?", session.ChatId).First(&chatItem)
 | 
			
		||||
			res = h.db.Where("chat_id = ?", session.ChatId).First(&chatItem)
 | 
			
		||||
			if res.Error != nil {
 | 
			
		||||
				chatItem.ChatId = session.ChatId
 | 
			
		||||
				chatItem.UserId = session.UserId
 | 
			
		||||
 
 | 
			
		||||
@@ -20,13 +20,16 @@ type qWenResp struct {
 | 
			
		||||
	Output struct {
 | 
			
		||||
		FinishReason string `json:"finish_reason"`
 | 
			
		||||
		Text         string `json:"text"`
 | 
			
		||||
	} `json:"output"`
 | 
			
		||||
	} `json:"output,omitempty"`
 | 
			
		||||
	Usage struct {
 | 
			
		||||
		TotalTokens  int `json:"total_tokens"`
 | 
			
		||||
		InputTokens  int `json:"input_tokens"`
 | 
			
		||||
		OutputTokens int `json:"output_tokens"`
 | 
			
		||||
	} `json:"usage"`
 | 
			
		||||
	} `json:"usage,omitempty"`
 | 
			
		||||
	RequestID string `json:"request_id"`
 | 
			
		||||
 | 
			
		||||
	Code    string `json:"code,omitempty"`
 | 
			
		||||
	Message string `json:"message,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 通义千问消息发送实现
 | 
			
		||||
@@ -70,6 +73,7 @@ func (h *ChatHandler) sendQWenMessage(
 | 
			
		||||
		scanner := bufio.NewScanner(response.Body)
 | 
			
		||||
 | 
			
		||||
		var content, lastText, newText string
 | 
			
		||||
		var outPutStart = false
 | 
			
		||||
 | 
			
		||||
		for scanner.Scan() {
 | 
			
		||||
			line := scanner.Text()
 | 
			
		||||
@@ -77,24 +81,32 @@ func (h *ChatHandler) sendQWenMessage(
 | 
			
		||||
				strings.HasPrefix(line, "event:") || strings.HasPrefix(line, ":HTTP_STATUS/200") {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if strings.HasPrefix(line, "data:") {
 | 
			
		||||
				content = line[5:]
 | 
			
		||||
			}
 | 
			
		||||
			// 处理代码换行
 | 
			
		||||
			if len(content) == 0 {
 | 
			
		||||
				content = "\n"
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			var resp qWenResp
 | 
			
		||||
			err := utils.JsonDecode(content, &resp)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logger.Error("error with parse data line: ", err)
 | 
			
		||||
				utils.ReplyMessage(ws, fmt.Sprintf("**解析数据行失败:%s**", err))
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if len(contents) == 0 { // 发送消息头
 | 
			
		||||
				utils.ReplyChunkMessage(ws, types.WsMessage{Type: types.WsStart})
 | 
			
		||||
				if !outPutStart {
 | 
			
		||||
					utils.ReplyChunkMessage(ws, types.WsMessage{Type: types.WsStart})
 | 
			
		||||
					outPutStart = true
 | 
			
		||||
					continue
 | 
			
		||||
				} else {
 | 
			
		||||
					// 处理代码换行
 | 
			
		||||
					content = "\n"
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				err := utils.JsonDecode(content, &resp)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					logger.Error("error with parse data line: ", content)
 | 
			
		||||
					utils.ReplyMessage(ws, fmt.Sprintf("**解析数据行失败:%s**", err))
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
				if resp.Message != "" {
 | 
			
		||||
					utils.ReplyMessage(ws, fmt.Sprintf("**API 返回错误:%s**", resp.Message))
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			//通过比较 lastText(上一次的文本)和 currentText(当前的文本),
 | 
			
		||||
@@ -135,66 +147,64 @@ func (h *ChatHandler) sendQWenMessage(
 | 
			
		||||
			useMsg := types.Message{Role: "user", Content: prompt}
 | 
			
		||||
 | 
			
		||||
			// 更新上下文消息,如果是调用函数则不需要更新上下文
 | 
			
		||||
			if h.App.ChatConfig.EnableContext {
 | 
			
		||||
			if h.App.SysConfig.EnableContext {
 | 
			
		||||
				chatCtx = append(chatCtx, useMsg)  // 提问消息
 | 
			
		||||
				chatCtx = append(chatCtx, message) // 回复消息
 | 
			
		||||
				h.App.ChatContexts.Put(session.ChatId, chatCtx)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// 追加聊天记录
 | 
			
		||||
			if h.App.ChatConfig.EnableHistory {
 | 
			
		||||
				// for prompt
 | 
			
		||||
				promptToken, err := utils.CalcTokens(prompt, req.Model)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					logger.Error(err)
 | 
			
		||||
				}
 | 
			
		||||
				historyUserMsg := model.ChatMessage{
 | 
			
		||||
					UserId:     userVo.Id,
 | 
			
		||||
					ChatId:     session.ChatId,
 | 
			
		||||
					RoleId:     role.Id,
 | 
			
		||||
					Type:       types.PromptMsg,
 | 
			
		||||
					Icon:       userVo.Avatar,
 | 
			
		||||
					Content:    template.HTMLEscapeString(prompt),
 | 
			
		||||
					Tokens:     promptToken,
 | 
			
		||||
					UseContext: true,
 | 
			
		||||
					Model:      req.Model,
 | 
			
		||||
				}
 | 
			
		||||
				historyUserMsg.CreatedAt = promptCreatedAt
 | 
			
		||||
				historyUserMsg.UpdatedAt = promptCreatedAt
 | 
			
		||||
				res := h.db.Save(&historyUserMsg)
 | 
			
		||||
				if res.Error != nil {
 | 
			
		||||
					logger.Error("failed to save prompt history message: ", res.Error)
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// for reply
 | 
			
		||||
				// 计算本次对话消耗的总 token 数量
 | 
			
		||||
				replyTokens, _ := utils.CalcTokens(message.Content, req.Model)
 | 
			
		||||
				totalTokens := replyTokens + getTotalTokens(req)
 | 
			
		||||
				historyReplyMsg := model.ChatMessage{
 | 
			
		||||
					UserId:     userVo.Id,
 | 
			
		||||
					ChatId:     session.ChatId,
 | 
			
		||||
					RoleId:     role.Id,
 | 
			
		||||
					Type:       types.ReplyMsg,
 | 
			
		||||
					Icon:       role.Icon,
 | 
			
		||||
					Content:    message.Content,
 | 
			
		||||
					Tokens:     totalTokens,
 | 
			
		||||
					UseContext: true,
 | 
			
		||||
					Model:      req.Model,
 | 
			
		||||
				}
 | 
			
		||||
				historyReplyMsg.CreatedAt = replyCreatedAt
 | 
			
		||||
				historyReplyMsg.UpdatedAt = replyCreatedAt
 | 
			
		||||
				res = h.db.Create(&historyReplyMsg)
 | 
			
		||||
				if res.Error != nil {
 | 
			
		||||
					logger.Error("failed to save reply history message: ", res.Error)
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// 更新用户算力
 | 
			
		||||
				h.subUserPower(userVo, session, promptToken, replyTokens)
 | 
			
		||||
			// for prompt
 | 
			
		||||
			promptToken, err := utils.CalcTokens(prompt, req.Model)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logger.Error(err)
 | 
			
		||||
			}
 | 
			
		||||
			historyUserMsg := model.ChatMessage{
 | 
			
		||||
				UserId:     userVo.Id,
 | 
			
		||||
				ChatId:     session.ChatId,
 | 
			
		||||
				RoleId:     role.Id,
 | 
			
		||||
				Type:       types.PromptMsg,
 | 
			
		||||
				Icon:       userVo.Avatar,
 | 
			
		||||
				Content:    template.HTMLEscapeString(prompt),
 | 
			
		||||
				Tokens:     promptToken,
 | 
			
		||||
				UseContext: true,
 | 
			
		||||
				Model:      req.Model,
 | 
			
		||||
			}
 | 
			
		||||
			historyUserMsg.CreatedAt = promptCreatedAt
 | 
			
		||||
			historyUserMsg.UpdatedAt = promptCreatedAt
 | 
			
		||||
			res := h.db.Save(&historyUserMsg)
 | 
			
		||||
			if res.Error != nil {
 | 
			
		||||
				logger.Error("failed to save prompt history message: ", res.Error)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// for reply
 | 
			
		||||
			// 计算本次对话消耗的总 token 数量
 | 
			
		||||
			replyTokens, _ := utils.CalcTokens(message.Content, req.Model)
 | 
			
		||||
			totalTokens := replyTokens + getTotalTokens(req)
 | 
			
		||||
			historyReplyMsg := model.ChatMessage{
 | 
			
		||||
				UserId:     userVo.Id,
 | 
			
		||||
				ChatId:     session.ChatId,
 | 
			
		||||
				RoleId:     role.Id,
 | 
			
		||||
				Type:       types.ReplyMsg,
 | 
			
		||||
				Icon:       role.Icon,
 | 
			
		||||
				Content:    message.Content,
 | 
			
		||||
				Tokens:     totalTokens,
 | 
			
		||||
				UseContext: true,
 | 
			
		||||
				Model:      req.Model,
 | 
			
		||||
			}
 | 
			
		||||
			historyReplyMsg.CreatedAt = replyCreatedAt
 | 
			
		||||
			historyReplyMsg.UpdatedAt = replyCreatedAt
 | 
			
		||||
			res = h.db.Create(&historyReplyMsg)
 | 
			
		||||
			if res.Error != nil {
 | 
			
		||||
				logger.Error("failed to save reply history message: ", res.Error)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// 更新用户算力
 | 
			
		||||
			h.subUserPower(userVo, session, promptToken, replyTokens)
 | 
			
		||||
 | 
			
		||||
			// 保存当前会话
 | 
			
		||||
			var chatItem model.ChatItem
 | 
			
		||||
			res := h.db.Where("chat_id = ?", session.ChatId).First(&chatItem)
 | 
			
		||||
			res = h.db.Where("chat_id = ?", session.ChatId).First(&chatItem)
 | 
			
		||||
			if res.Error != nil {
 | 
			
		||||
				chatItem.ChatId = session.ChatId
 | 
			
		||||
				chatItem.UserId = session.UserId
 | 
			
		||||
 
 | 
			
		||||
@@ -50,9 +50,10 @@ type xunFeiResp struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var Model2URL = map[string]string{
 | 
			
		||||
	"general":   "v1.1",
 | 
			
		||||
	"generalv2": "v2.1",
 | 
			
		||||
	"generalv3": "v3.1",
 | 
			
		||||
	"general":     "v1.1",
 | 
			
		||||
	"generalv2":   "v2.1",
 | 
			
		||||
	"generalv3":   "v3.1",
 | 
			
		||||
	"generalv3.5": "v3.5",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 科大讯飞消息发送实现
 | 
			
		||||
@@ -86,6 +87,7 @@ func (h *ChatHandler) sendXunFeiMessage(
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	apiURL := strings.Replace(apiKey.ApiURL, "{version}", Model2URL[req.Model], 1)
 | 
			
		||||
	logger.Debugf("Sending %s request, ApiURL:%s, API KEY:%s, PROXY: %s, Model: %s", session.Model.Platform, apiURL, apiKey.Value, apiKey.ProxyURL, req.Model)
 | 
			
		||||
	wsURL, err := assembleAuthUrl(apiURL, key[1], key[2])
 | 
			
		||||
	//握手并建立websocket 连接
 | 
			
		||||
	conn, resp, err := d.Dial(wsURL, nil)
 | 
			
		||||
@@ -173,66 +175,64 @@ func (h *ChatHandler) sendXunFeiMessage(
 | 
			
		||||
		useMsg := types.Message{Role: "user", Content: prompt}
 | 
			
		||||
 | 
			
		||||
		// 更新上下文消息,如果是调用函数则不需要更新上下文
 | 
			
		||||
		if h.App.ChatConfig.EnableContext {
 | 
			
		||||
		if h.App.SysConfig.EnableContext {
 | 
			
		||||
			chatCtx = append(chatCtx, useMsg)  // 提问消息
 | 
			
		||||
			chatCtx = append(chatCtx, message) // 回复消息
 | 
			
		||||
			h.App.ChatContexts.Put(session.ChatId, chatCtx)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// 追加聊天记录
 | 
			
		||||
		if h.App.ChatConfig.EnableHistory {
 | 
			
		||||
			// for prompt
 | 
			
		||||
			promptToken, err := utils.CalcTokens(prompt, req.Model)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logger.Error(err)
 | 
			
		||||
			}
 | 
			
		||||
			historyUserMsg := model.ChatMessage{
 | 
			
		||||
				UserId:     userVo.Id,
 | 
			
		||||
				ChatId:     session.ChatId,
 | 
			
		||||
				RoleId:     role.Id,
 | 
			
		||||
				Type:       types.PromptMsg,
 | 
			
		||||
				Icon:       userVo.Avatar,
 | 
			
		||||
				Content:    template.HTMLEscapeString(prompt),
 | 
			
		||||
				Tokens:     promptToken,
 | 
			
		||||
				UseContext: true,
 | 
			
		||||
				Model:      req.Model,
 | 
			
		||||
			}
 | 
			
		||||
			historyUserMsg.CreatedAt = promptCreatedAt
 | 
			
		||||
			historyUserMsg.UpdatedAt = promptCreatedAt
 | 
			
		||||
			res := h.db.Save(&historyUserMsg)
 | 
			
		||||
			if res.Error != nil {
 | 
			
		||||
				logger.Error("failed to save prompt history message: ", res.Error)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// for reply
 | 
			
		||||
			// 计算本次对话消耗的总 token 数量
 | 
			
		||||
			replyTokens, _ := utils.CalcTokens(message.Content, req.Model)
 | 
			
		||||
			totalTokens := replyTokens + getTotalTokens(req)
 | 
			
		||||
			historyReplyMsg := model.ChatMessage{
 | 
			
		||||
				UserId:     userVo.Id,
 | 
			
		||||
				ChatId:     session.ChatId,
 | 
			
		||||
				RoleId:     role.Id,
 | 
			
		||||
				Type:       types.ReplyMsg,
 | 
			
		||||
				Icon:       role.Icon,
 | 
			
		||||
				Content:    message.Content,
 | 
			
		||||
				Tokens:     totalTokens,
 | 
			
		||||
				UseContext: true,
 | 
			
		||||
				Model:      req.Model,
 | 
			
		||||
			}
 | 
			
		||||
			historyReplyMsg.CreatedAt = replyCreatedAt
 | 
			
		||||
			historyReplyMsg.UpdatedAt = replyCreatedAt
 | 
			
		||||
			res = h.db.Create(&historyReplyMsg)
 | 
			
		||||
			if res.Error != nil {
 | 
			
		||||
				logger.Error("failed to save reply history message: ", res.Error)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// 更新用户算力
 | 
			
		||||
			h.subUserPower(userVo, session, promptToken, replyTokens)
 | 
			
		||||
		// for prompt
 | 
			
		||||
		promptToken, err := utils.CalcTokens(prompt, req.Model)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logger.Error(err)
 | 
			
		||||
		}
 | 
			
		||||
		historyUserMsg := model.ChatMessage{
 | 
			
		||||
			UserId:     userVo.Id,
 | 
			
		||||
			ChatId:     session.ChatId,
 | 
			
		||||
			RoleId:     role.Id,
 | 
			
		||||
			Type:       types.PromptMsg,
 | 
			
		||||
			Icon:       userVo.Avatar,
 | 
			
		||||
			Content:    template.HTMLEscapeString(prompt),
 | 
			
		||||
			Tokens:     promptToken,
 | 
			
		||||
			UseContext: true,
 | 
			
		||||
			Model:      req.Model,
 | 
			
		||||
		}
 | 
			
		||||
		historyUserMsg.CreatedAt = promptCreatedAt
 | 
			
		||||
		historyUserMsg.UpdatedAt = promptCreatedAt
 | 
			
		||||
		res := h.db.Save(&historyUserMsg)
 | 
			
		||||
		if res.Error != nil {
 | 
			
		||||
			logger.Error("failed to save prompt history message: ", res.Error)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// for reply
 | 
			
		||||
		// 计算本次对话消耗的总 token 数量
 | 
			
		||||
		replyTokens, _ := utils.CalcTokens(message.Content, req.Model)
 | 
			
		||||
		totalTokens := replyTokens + getTotalTokens(req)
 | 
			
		||||
		historyReplyMsg := model.ChatMessage{
 | 
			
		||||
			UserId:     userVo.Id,
 | 
			
		||||
			ChatId:     session.ChatId,
 | 
			
		||||
			RoleId:     role.Id,
 | 
			
		||||
			Type:       types.ReplyMsg,
 | 
			
		||||
			Icon:       role.Icon,
 | 
			
		||||
			Content:    message.Content,
 | 
			
		||||
			Tokens:     totalTokens,
 | 
			
		||||
			UseContext: true,
 | 
			
		||||
			Model:      req.Model,
 | 
			
		||||
		}
 | 
			
		||||
		historyReplyMsg.CreatedAt = replyCreatedAt
 | 
			
		||||
		historyReplyMsg.UpdatedAt = replyCreatedAt
 | 
			
		||||
		res = h.db.Create(&historyReplyMsg)
 | 
			
		||||
		if res.Error != nil {
 | 
			
		||||
			logger.Error("failed to save reply history message: ", res.Error)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// 更新用户算力
 | 
			
		||||
		h.subUserPower(userVo, session, promptToken, replyTokens)
 | 
			
		||||
 | 
			
		||||
		// 保存当前会话
 | 
			
		||||
		var chatItem model.ChatItem
 | 
			
		||||
		res := h.db.Where("chat_id = ?", session.ChatId).First(&chatItem)
 | 
			
		||||
		res = h.db.Where("chat_id = ?", session.ChatId).First(&chatItem)
 | 
			
		||||
		if res.Error != nil {
 | 
			
		||||
			chatItem.ChatId = session.ChatId
 | 
			
		||||
			chatItem.UserId = session.UserId
 | 
			
		||||
@@ -260,7 +260,7 @@ func buildRequest(appid string, req types.ApiRequest) map[string]interface{} {
 | 
			
		||||
		"parameter": map[string]interface{}{
 | 
			
		||||
			"chat": map[string]interface{}{
 | 
			
		||||
				"domain":      req.Model,
 | 
			
		||||
				"temperature": float64(req.Temperature),
 | 
			
		||||
				"temperature": req.Temperature,
 | 
			
		||||
				"top_k":       int64(6),
 | 
			
		||||
				"max_tokens":  int64(req.MaxTokens),
 | 
			
		||||
				"auditing":    "default",
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,6 @@ type FunctionHandler struct {
 | 
			
		||||
	db            *gorm.DB
 | 
			
		||||
	config        types.ChatPlusApiConfig
 | 
			
		||||
	uploadManager *oss.UploaderManager
 | 
			
		||||
	proxyURL      string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewFunctionHandler(server *core.AppServer, db *gorm.DB, config *types.AppConfig, manager *oss.UploaderManager) *FunctionHandler {
 | 
			
		||||
@@ -33,7 +32,6 @@ func NewFunctionHandler(server *core.AppServer, db *gorm.DB, config *types.AppCo
 | 
			
		||||
		db:            db,
 | 
			
		||||
		config:        config.ApiConfig,
 | 
			
		||||
		uploadManager: manager,
 | 
			
		||||
		proxyURL:      config.ProxyURL,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -213,47 +211,28 @@ func (h *FunctionHandler) Dall3(c *gin.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// get image generation api URL
 | 
			
		||||
	var conf model.Config
 | 
			
		||||
	var chatConfig types.ChatConfig
 | 
			
		||||
	tx = h.db.Where("marker", "chat").First(&conf)
 | 
			
		||||
	if tx.Error != nil {
 | 
			
		||||
		resp.ERROR(c, "error with get chat configs:"+tx.Error.Error())
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err := utils.JsonDecode(conf.Config, &chatConfig)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		resp.ERROR(c, "error with decode chat config: "+err.Error())
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// translate prompt
 | 
			
		||||
	const translatePromptTemplate = "Translate the following painting prompt words into English keyword phrases. Without any explanation, directly output the keyword phrases separated by commas. The content to be translated is: [%s]"
 | 
			
		||||
	pt, err := utils.OpenAIRequest(h.db, fmt.Sprintf(translatePromptTemplate, params["prompt"]), h.App.Config.ProxyURL)
 | 
			
		||||
	pt, err := utils.OpenAIRequest(h.db, fmt.Sprintf(translatePromptTemplate, params["prompt"]))
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		logger.Debugf("翻译绘画提示词,原文:%s,译文:%s", prompt, pt)
 | 
			
		||||
		prompt = pt
 | 
			
		||||
	}
 | 
			
		||||
	imgNum := chatConfig.DallImgNum
 | 
			
		||||
	if imgNum <= 0 {
 | 
			
		||||
		imgNum = 1
 | 
			
		||||
	}
 | 
			
		||||
	var res imgRes
 | 
			
		||||
	var errRes ErrRes
 | 
			
		||||
	var request *req.Request
 | 
			
		||||
	if apiKey.UseProxy && h.proxyURL != "" {
 | 
			
		||||
		request = req.C().SetProxyURL(h.proxyURL).R()
 | 
			
		||||
	if apiKey.ProxyURL != "" {
 | 
			
		||||
		request = req.C().SetProxyURL(apiKey.ProxyURL).R()
 | 
			
		||||
	} else {
 | 
			
		||||
		request = req.C().R()
 | 
			
		||||
	}
 | 
			
		||||
	logger.Debugf("Sending %s request, ApiURL:%s, API KEY:%s, PROXY: %s", apiKey.Platform, apiKey.ApiURL, apiKey.Value, h.proxyURL)
 | 
			
		||||
	logger.Debugf("Sending %s request, ApiURL:%s, API KEY:%s, PROXY: %s", apiKey.Platform, apiKey.ApiURL, apiKey.Value, apiKey.ProxyURL)
 | 
			
		||||
	r, err := request.SetHeader("Content-Type", "application/json").
 | 
			
		||||
		SetHeader("Authorization", "Bearer "+apiKey.Value).
 | 
			
		||||
		SetBody(imgReq{
 | 
			
		||||
			Model:  "dall-e-3",
 | 
			
		||||
			Prompt: prompt,
 | 
			
		||||
			N:      imgNum,
 | 
			
		||||
			N:      1,
 | 
			
		||||
			Size:   "1024x1024",
 | 
			
		||||
		}).
 | 
			
		||||
		SetErrorResult(&errRes).
 | 
			
		||||
 
 | 
			
		||||
@@ -35,7 +35,7 @@ func (h *PromptHandler) Rewrite(c *gin.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	content, err := utils.OpenAIRequest(h.db, fmt.Sprintf(rewritePromptTemplate, data.Prompt), h.App.Config.ProxyURL)
 | 
			
		||||
	content, err := utils.OpenAIRequest(h.db, fmt.Sprintf(rewritePromptTemplate, data.Prompt))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		resp.ERROR(c, err.Error())
 | 
			
		||||
		return
 | 
			
		||||
@@ -53,7 +53,7 @@ func (h *PromptHandler) Translate(c *gin.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	content, err := utils.OpenAIRequest(h.db, fmt.Sprintf(translatePromptTemplate, data.Prompt), h.App.Config.ProxyURL)
 | 
			
		||||
	content, err := utils.OpenAIRequest(h.db, fmt.Sprintf(translatePromptTemplate, data.Prompt))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		resp.ERROR(c, err.Error())
 | 
			
		||||
		return
 | 
			
		||||
 
 | 
			
		||||
@@ -95,14 +95,7 @@ func (h *UserHandler) Register(c *gin.Context) {
 | 
			
		||||
		Status:     true,
 | 
			
		||||
		ChatRoles:  utils.JsonEncode([]string{"gpt"}),               // 默认只订阅通用助手角色
 | 
			
		||||
		ChatModels: utils.JsonEncode(h.App.SysConfig.DefaultModels), // 默认开通的模型
 | 
			
		||||
		ChatConfig: utils.JsonEncode(types.UserChatConfig{
 | 
			
		||||
			ApiKeys: map[types.Platform]string{
 | 
			
		||||
				types.OpenAI:  "",
 | 
			
		||||
				types.Azure:   "",
 | 
			
		||||
				types.ChatGLM: "",
 | 
			
		||||
			},
 | 
			
		||||
		}),
 | 
			
		||||
		Power: h.App.SysConfig.InitPower,
 | 
			
		||||
		Power:      h.App.SysConfig.InitPower,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	res = h.db.Create(&user)
 | 
			
		||||
@@ -245,14 +238,13 @@ func (h *UserHandler) Session(c *gin.Context) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type userProfile struct {
 | 
			
		||||
	Id          uint                 `json:"id"`
 | 
			
		||||
	Nickname    string               `json:"nickname"`
 | 
			
		||||
	Username    string               `json:"username"`
 | 
			
		||||
	Avatar      string               `json:"avatar"`
 | 
			
		||||
	ChatConfig  types.UserChatConfig `json:"chat_config"`
 | 
			
		||||
	Power       int                  `json:"power"`
 | 
			
		||||
	ExpiredTime int64                `json:"expired_time"`
 | 
			
		||||
	Vip         bool                 `json:"vip"`
 | 
			
		||||
	Id          uint   `json:"id"`
 | 
			
		||||
	Nickname    string `json:"nickname"`
 | 
			
		||||
	Username    string `json:"username"`
 | 
			
		||||
	Avatar      string `json:"avatar"`
 | 
			
		||||
	Power       int    `json:"power"`
 | 
			
		||||
	ExpiredTime int64  `json:"expired_time"`
 | 
			
		||||
	Vip         bool   `json:"vip"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *UserHandler) Profile(c *gin.Context) {
 | 
			
		||||
 
 | 
			
		||||
@@ -315,7 +315,7 @@ func main() {
 | 
			
		||||
			group.GET("list", h.List)
 | 
			
		||||
			group.POST("set", h.Set)
 | 
			
		||||
			group.POST("sort", h.Sort)
 | 
			
		||||
			group.POST("remove", h.Remove)
 | 
			
		||||
			group.GET("remove", h.Remove)
 | 
			
		||||
		}),
 | 
			
		||||
		fx.Invoke(func(s *core.AppServer, h *handler.PaymentHandler) {
 | 
			
		||||
			group := s.Engine.Group("/api/payment/")
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,6 @@ type ApiKey struct {
 | 
			
		||||
	Value      string // API Key 的值
 | 
			
		||||
	ApiURL     string // 当前 KEY 的 API 地址
 | 
			
		||||
	Enabled    bool   // 是否启用
 | 
			
		||||
	UseProxy   bool   // 是否使用代理访问 API URL
 | 
			
		||||
	ProxyURL   string // 代理地址
 | 
			
		||||
	LastUsedAt int64  // 最后使用时间
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,11 +2,14 @@ package model
 | 
			
		||||
 | 
			
		||||
type ChatModel struct {
 | 
			
		||||
	BaseModel
 | 
			
		||||
	Platform string
 | 
			
		||||
	Name     string
 | 
			
		||||
	Value    string // API Key 的值
 | 
			
		||||
	SortNum  int
 | 
			
		||||
	Enabled  bool
 | 
			
		||||
	Power    int  // 每次对话消耗算力
 | 
			
		||||
	Open     bool // 是否开放模型给所有人使用
 | 
			
		||||
	Platform    string
 | 
			
		||||
	Name        string
 | 
			
		||||
	Value       string // API Key 的值
 | 
			
		||||
	SortNum     int
 | 
			
		||||
	Enabled     bool
 | 
			
		||||
	Power       int     // 每次对话消耗算力
 | 
			
		||||
	Open        bool    // 是否开放模型给所有人使用
 | 
			
		||||
	MaxTokens   int     // 最大响应长度
 | 
			
		||||
	MaxContext  int     // 最大上下文长度
 | 
			
		||||
	Temperature float32 // 模型温度
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,6 @@ type ApiKey struct {
 | 
			
		||||
	Value      string `json:"value"` // API Key 的值
 | 
			
		||||
	ApiURL     string `json:"api_url"`
 | 
			
		||||
	Enabled    bool   `json:"enabled"`
 | 
			
		||||
	UseProxy   bool   `json:"use_proxy"`
 | 
			
		||||
	ProxyURL   string `json:"proxy_url"`
 | 
			
		||||
	LastUsedAt int64  `json:"last_used_at"` // 最后使用时间
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,11 +2,14 @@ package vo
 | 
			
		||||
 | 
			
		||||
type ChatModel struct {
 | 
			
		||||
	BaseVo
 | 
			
		||||
	Platform string `json:"platform"`
 | 
			
		||||
	Name     string `json:"name"`
 | 
			
		||||
	Value    string `json:"value"`
 | 
			
		||||
	Enabled  bool   `json:"enabled"`
 | 
			
		||||
	SortNum  int    `json:"sort_num"`
 | 
			
		||||
	Weight   int    `json:"weight"`
 | 
			
		||||
	Open     bool   `json:"open"`
 | 
			
		||||
	Platform    string  `json:"platform"`
 | 
			
		||||
	Name        string  `json:"name"`
 | 
			
		||||
	Value       string  `json:"value"`
 | 
			
		||||
	Enabled     bool    `json:"enabled"`
 | 
			
		||||
	SortNum     int     `json:"sort_num"`
 | 
			
		||||
	Power       int     `json:"power"`
 | 
			
		||||
	Open        bool    `json:"open"`
 | 
			
		||||
	MaxTokens   int     `json:"max_tokens"`  // 最大响应长度
 | 
			
		||||
	MaxContext  int     `json:"max_context"` // 最大上下文长度
 | 
			
		||||
	Temperature float32 `json:"temperature"` // 模型温度
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,5 @@ import "chatplus/core/types"
 | 
			
		||||
type Config struct {
 | 
			
		||||
	Id           uint               `json:"id"`
 | 
			
		||||
	Key          string             `json:"key"`
 | 
			
		||||
	ChatConfig   types.ChatConfig   `json:"chat_config"`
 | 
			
		||||
	SystemConfig types.SystemConfig `json:"system_config"`
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,20 +1,17 @@
 | 
			
		||||
package vo
 | 
			
		||||
 | 
			
		||||
import "chatplus/core/types"
 | 
			
		||||
 | 
			
		||||
type User struct {
 | 
			
		||||
	BaseVo
 | 
			
		||||
	Username    string               `json:"username"`
 | 
			
		||||
	Nickname    string               `json:"nickname"`
 | 
			
		||||
	Avatar      string               `json:"avatar"`
 | 
			
		||||
	Salt        string               `json:"salt"`          // 密码盐
 | 
			
		||||
	Power       int                  `json:"power"`         // 剩余算力
 | 
			
		||||
	ChatConfig  types.UserChatConfig `json:"chat_config"`   // 聊天配置
 | 
			
		||||
	ChatRoles   []string             `json:"chat_roles"`    // 聊天角色集合
 | 
			
		||||
	ChatModels  []string             `json:"chat_models"`   // AI模型集合
 | 
			
		||||
	ExpiredTime int64                `json:"expired_time"`  // 账户到期时间
 | 
			
		||||
	Status      bool                 `json:"status"`        // 当前状态
 | 
			
		||||
	LastLoginAt int64                `json:"last_login_at"` // 最后登录时间
 | 
			
		||||
	LastLoginIp string               `json:"last_login_ip"` // 最后登录 IP
 | 
			
		||||
	Vip         bool                 `json:"vip"`
 | 
			
		||||
	Username    string   `json:"username"`
 | 
			
		||||
	Nickname    string   `json:"nickname"`
 | 
			
		||||
	Avatar      string   `json:"avatar"`
 | 
			
		||||
	Salt        string   `json:"salt"`          // 密码盐
 | 
			
		||||
	Power       int      `json:"power"`         // 剩余算力
 | 
			
		||||
	ChatRoles   []string `json:"chat_roles"`    // 聊天角色集合
 | 
			
		||||
	ChatModels  []string `json:"chat_models"`   // AI模型集合
 | 
			
		||||
	ExpiredTime int64    `json:"expired_time"`  // 账户到期时间
 | 
			
		||||
	Status      bool     `json:"status"`        // 当前状态
 | 
			
		||||
	LastLoginAt int64    `json:"last_login_at"` // 最后登录时间
 | 
			
		||||
	LastLoginIp string   `json:"last_login_ip"` // 最后登录 IP
 | 
			
		||||
	Vip         bool     `json:"vip"`
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -88,7 +88,7 @@ type apiErrRes struct {
 | 
			
		||||
	} `json:"error"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func OpenAIRequest(db *gorm.DB, prompt string, proxy string) (string, error) {
 | 
			
		||||
func OpenAIRequest(db *gorm.DB, prompt string) (string, error) {
 | 
			
		||||
	var apiKey model.ApiKey
 | 
			
		||||
	res := db.Where("platform = ?", types.OpenAI).Where("type = ?", "chat").Where("enabled = ?", true).First(&apiKey)
 | 
			
		||||
	if res.Error != nil {
 | 
			
		||||
@@ -104,8 +104,8 @@ func OpenAIRequest(db *gorm.DB, prompt string, proxy string) (string, error) {
 | 
			
		||||
	var response apiRes
 | 
			
		||||
	var errRes apiErrRes
 | 
			
		||||
	client := req.C()
 | 
			
		||||
	if apiKey.UseProxy && proxy != "" {
 | 
			
		||||
		client.SetProxyURL(proxy)
 | 
			
		||||
	if apiKey.ProxyURL != "" {
 | 
			
		||||
		client.SetProxyURL(apiKey.ApiURL)
 | 
			
		||||
	}
 | 
			
		||||
	r, err := client.R().SetHeader("Content-Type", "application/json").
 | 
			
		||||
		SetHeader("Authorization", "Bearer "+apiKey.Value).
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"math/rand"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/crypto/sha3"
 | 
			
		||||
@@ -59,7 +60,7 @@ func Str2stamp(str string) int64 {
 | 
			
		||||
	if len(str) == 0 {
 | 
			
		||||
		return 0
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	layout := "2006-01-02 15:04:05"
 | 
			
		||||
	t, err := time.Parse(layout, str)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -92,3 +93,11 @@ func InterfaceToString(value interface{}) string {
 | 
			
		||||
	}
 | 
			
		||||
	return JsonEncode(value)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Str2Float(str string) float64 {
 | 
			
		||||
	num, err := strconv.ParseFloat(str, 64)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return 0
 | 
			
		||||
	}
 | 
			
		||||
	return num
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -130,3 +130,8 @@ INSERT INTO `chatgpt_admin_permissions` VALUES (31, '权限配置', '', 2, 28, '
 | 
			
		||||
INSERT INTO `chatgpt_admin_permissions` VALUES (32, '角色配置', '', 3, 28, '2024-03-14 15:29:15', '2024-03-14 15:29:15');
 | 
			
		||||
INSERT INTO `chatgpt_admin_permissions` VALUES (33, '列表', 'api_admin_sysPermission_list', 1, 31, '2024-03-14 15:29:52', '2024-03-14 15:29:52');
 | 
			
		||||
INSERT INTO `chatgpt_admin_permissions` VALUES (34, '列表', 'api_admin_sysRole_list', 1, 32, '2024-03-14 15:30:21', '2024-03-14 15:30:21');
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
ALTER TABLE `chatgpt_api_keys` CHANGE `use_proxy` `proxy_url` VARCHAR(100) NULL DEFAULT NULL COMMENT '代理地址';
 | 
			
		||||
-- 重置 proxy_url
 | 
			
		||||
UPDATE chatgpt_api_keys  set proxy_url=''
 | 
			
		||||
							
								
								
									
										99
									
								
								new-ui/pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										99
									
								
								new-ui/pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							@@ -1,4 +1,4 @@
 | 
			
		||||
lockfileVersion: '6.0'
 | 
			
		||||
lockfileVersion: '6.1'
 | 
			
		||||
 | 
			
		||||
settings:
 | 
			
		||||
  autoInstallPeers: true
 | 
			
		||||
@@ -127,103 +127,6 @@ importers:
 | 
			
		||||
        specifier: ^1.8.27
 | 
			
		||||
        version: 1.8.27(typescript@5.3.3)
 | 
			
		||||
 | 
			
		||||
  projects/mobile:
 | 
			
		||||
    dependencies:
 | 
			
		||||
      '@element-plus/icons-vue':
 | 
			
		||||
        specifier: ^2.1.0
 | 
			
		||||
        version: 2.3.1(vue@3.4.21)
 | 
			
		||||
      axios:
 | 
			
		||||
        specifier: ^0.27.2
 | 
			
		||||
        version: 0.27.2
 | 
			
		||||
      clipboard:
 | 
			
		||||
        specifier: ^2.0.11
 | 
			
		||||
        version: 2.0.11
 | 
			
		||||
      compressorjs:
 | 
			
		||||
        specifier: ^1.2.1
 | 
			
		||||
        version: 1.2.1
 | 
			
		||||
      core-js:
 | 
			
		||||
        specifier: ^3.8.3
 | 
			
		||||
        version: 3.36.0
 | 
			
		||||
      element-plus:
 | 
			
		||||
        specifier: ^2.3.0
 | 
			
		||||
        version: 2.6.1(vue@3.4.21)
 | 
			
		||||
      good-storage:
 | 
			
		||||
        specifier: ^1.1.1
 | 
			
		||||
        version: 1.1.1
 | 
			
		||||
      highlight.js:
 | 
			
		||||
        specifier: ^11.7.0
 | 
			
		||||
        version: 11.9.0
 | 
			
		||||
      json-bigint:
 | 
			
		||||
        specifier: ^1.0.0
 | 
			
		||||
        version: 1.0.0
 | 
			
		||||
      lodash:
 | 
			
		||||
        specifier: ^4.17.21
 | 
			
		||||
        version: 4.17.21
 | 
			
		||||
      markdown-it:
 | 
			
		||||
        specifier: ^13.0.1
 | 
			
		||||
        version: 13.0.2
 | 
			
		||||
      markdown-it-latex2img:
 | 
			
		||||
        specifier: ^0.0.6
 | 
			
		||||
        version: 0.0.6
 | 
			
		||||
      markdown-it-mathjax:
 | 
			
		||||
        specifier: ^2.0.0
 | 
			
		||||
        version: 2.0.0
 | 
			
		||||
      md-editor-v3:
 | 
			
		||||
        specifier: ^2.2.1
 | 
			
		||||
        version: 2.11.3(vue@3.4.21)
 | 
			
		||||
      pinia:
 | 
			
		||||
        specifier: ^2.1.4
 | 
			
		||||
        version: 2.1.7(typescript@5.3.3)(vue@3.4.21)
 | 
			
		||||
      qrcode:
 | 
			
		||||
        specifier: ^1.5.3
 | 
			
		||||
        version: 1.5.3
 | 
			
		||||
      qs:
 | 
			
		||||
        specifier: ^6.11.1
 | 
			
		||||
        version: 6.12.0
 | 
			
		||||
      sortablejs:
 | 
			
		||||
        specifier: ^1.15.0
 | 
			
		||||
        version: 1.15.2
 | 
			
		||||
      v3-waterfall:
 | 
			
		||||
        specifier: ^1.2.1
 | 
			
		||||
        version: 1.3.3
 | 
			
		||||
      vant:
 | 
			
		||||
        specifier: ^4.5.0
 | 
			
		||||
        version: 4.8.5(vue@3.4.21)
 | 
			
		||||
      vue:
 | 
			
		||||
        specifier: ^3.2.13
 | 
			
		||||
        version: 3.4.21(typescript@5.3.3)
 | 
			
		||||
      vue-router:
 | 
			
		||||
        specifier: ^4.0.15
 | 
			
		||||
        version: 4.3.0(vue@3.4.21)
 | 
			
		||||
    devDependencies:
 | 
			
		||||
      '@babel/core':
 | 
			
		||||
        specifier: 7.18.6
 | 
			
		||||
        version: 7.18.6
 | 
			
		||||
      '@babel/eslint-parser':
 | 
			
		||||
        specifier: ^7.12.16
 | 
			
		||||
        version: 7.23.10(@babel/core@7.18.6)(eslint@7.32.0)
 | 
			
		||||
      '@vue/cli-plugin-babel':
 | 
			
		||||
        specifier: ~5.0.0
 | 
			
		||||
        version: 5.0.8(@vue/cli-service@5.0.8)(core-js@3.36.0)(vue@3.4.21)
 | 
			
		||||
      '@vue/cli-plugin-eslint':
 | 
			
		||||
        specifier: ~5.0.0
 | 
			
		||||
        version: 5.0.8(@vue/cli-service@5.0.8)(eslint@7.32.0)
 | 
			
		||||
      '@vue/cli-service':
 | 
			
		||||
        specifier: ~5.0.0
 | 
			
		||||
        version: 5.0.8(lodash@4.17.21)(prettier@3.2.5)(stylus-loader@7.1.3)(vue@3.4.21)
 | 
			
		||||
      eslint:
 | 
			
		||||
        specifier: ^7.32.0
 | 
			
		||||
        version: 7.32.0
 | 
			
		||||
      eslint-plugin-vue:
 | 
			
		||||
        specifier: ^8.0.3
 | 
			
		||||
        version: 8.7.1(eslint@7.32.0)
 | 
			
		||||
      stylus:
 | 
			
		||||
        specifier: ^0.58.1
 | 
			
		||||
        version: 0.58.1
 | 
			
		||||
      stylus-loader:
 | 
			
		||||
        specifier: ^7.0.0
 | 
			
		||||
        version: 7.1.3(stylus@0.58.1)(webpack@5.90.3)
 | 
			
		||||
 | 
			
		||||
  projects/web:
 | 
			
		||||
    dependencies:
 | 
			
		||||
      '@element-plus/icons-vue':
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
  "recommendations": [
 | 
			
		||||
    "Vue.volar",
 | 
			
		||||
    "Vue.vscode-typescript-vue-plugin",
 | 
			
		||||
    "dbaeumer.vscode-eslint"
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										49
									
								
								new-ui/projects/admin/src/components/CustomUploader.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								new-ui/projects/admin/src/components/CustomUploader.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { computed } from "vue";
 | 
			
		||||
import { Message } from "@arco-design/web-vue";
 | 
			
		||||
import type { UploadInstance, FileItem } from "@arco-design/web-vue";
 | 
			
		||||
import { uploadUrl } from "@/http/config";
 | 
			
		||||
 | 
			
		||||
defineProps({
 | 
			
		||||
  modelValue: String,
 | 
			
		||||
  placeholder: String,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const emits = defineEmits(["update:modelValue"]);
 | 
			
		||||
 | 
			
		||||
const uploadProps = computed<UploadInstance["$props"]>(() => {
 | 
			
		||||
  const TOKEN = JSON.parse(localStorage.getItem(__AUTH_KEY))?.token;
 | 
			
		||||
  return {
 | 
			
		||||
    accept: "image/*",
 | 
			
		||||
    action: uploadUrl,
 | 
			
		||||
    name: "file",
 | 
			
		||||
    headers: { [__AUTH_KEY]: TOKEN },
 | 
			
		||||
    showFileList: false,
 | 
			
		||||
  };
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const handleChange = (_, file: FileItem) => {
 | 
			
		||||
  if (file?.response) {
 | 
			
		||||
    emits("update:modelValue", file?.response?.data?.url);
 | 
			
		||||
    Message.success("上传成功");
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<template>
 | 
			
		||||
  <a-upload v-bind="uploadProps" style="width: 100%" @change="handleChange">
 | 
			
		||||
    <template #upload-button>
 | 
			
		||||
      <a-input-group style="width: 100%">
 | 
			
		||||
        <a-input
 | 
			
		||||
          :model-value="modelValue"
 | 
			
		||||
          :placeholder="placeholder"
 | 
			
		||||
          readonly
 | 
			
		||||
          allow-clear
 | 
			
		||||
          @clear.stop="emits('update:modelValue')"
 | 
			
		||||
        />
 | 
			
		||||
        <a-button type="primary" style="width: 100px">
 | 
			
		||||
          <icon-cloud />
 | 
			
		||||
        </a-button>
 | 
			
		||||
      </a-input-group>
 | 
			
		||||
    </template>
 | 
			
		||||
  </a-upload>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -1,18 +1,17 @@
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { computed, ref, onActivated } from "vue";
 | 
			
		||||
import { computed, onActivated } from "vue";
 | 
			
		||||
import useAsyncTable from "./useAsyncTable";
 | 
			
		||||
import { useTableScroll } from "@/components/SearchTable/utils";
 | 
			
		||||
import { Message } from "@arco-design/web-vue";
 | 
			
		||||
import { Message, type TableColumnData } from "@arco-design/web-vue";
 | 
			
		||||
import type { TableRequest, TableOriginalProps } from "./useAsyncTable";
 | 
			
		||||
 | 
			
		||||
interface SimpleTable extends /* @vue-ignore */ TableOriginalProps {
 | 
			
		||||
  request: TableRequest<Record<string, unknown>>;
 | 
			
		||||
  params?: Record<string, unknown>;
 | 
			
		||||
  columns?: TableOriginalProps["columns"];
 | 
			
		||||
  columns?: TableColumnData[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const props = defineProps<SimpleTable>();
 | 
			
		||||
const tableContainerRef = ref<HTMLElement>();
 | 
			
		||||
 | 
			
		||||
// 表格请求参数
 | 
			
		||||
const [tableConfig, getList] = useAsyncTable(props.request, props.params);
 | 
			
		||||
@@ -45,7 +44,7 @@ onActivated(handleSearch);
 | 
			
		||||
          ...$attrs,
 | 
			
		||||
          ...tableConfig,
 | 
			
		||||
          ...props,
 | 
			
		||||
          scroll: useTableScroll(_columns || [], tableContainerRef as HTMLElement),
 | 
			
		||||
          scroll: useTableScroll(_columns || []),
 | 
			
		||||
          columns: _columns,
 | 
			
		||||
        }"
 | 
			
		||||
      >
 | 
			
		||||
 
 | 
			
		||||
@@ -2,11 +2,11 @@
 | 
			
		||||
import { getList, save, deleting, setStatus } from "./api";
 | 
			
		||||
import ApiKeyForm from "./ApiKeyForm.vue";
 | 
			
		||||
import useCustomFormPopup from "@/composables/useCustomFormPopup";
 | 
			
		||||
import { Message } from "@arco-design/web-vue";
 | 
			
		||||
import { Message, type TableColumnData } from "@arco-design/web-vue";
 | 
			
		||||
import SimpleTable from "@/components/SimpleTable/SimpleTable.vue";
 | 
			
		||||
import { dateFormat } from "@chatgpt-plus/packages/utils";
 | 
			
		||||
// table 配置
 | 
			
		||||
const columns = [
 | 
			
		||||
const columns: TableColumnData[] = [
 | 
			
		||||
  {
 | 
			
		||||
    title: "所属平台",
 | 
			
		||||
    dataIndex: "platform",
 | 
			
		||||
@@ -20,6 +20,11 @@ const columns = [
 | 
			
		||||
    dataIndex: "value",
 | 
			
		||||
    slotName: "value",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    title: "API URL",
 | 
			
		||||
    dataIndex: "api_url",
 | 
			
		||||
    slotName: "value",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    title: "用途",
 | 
			
		||||
    dataIndex: "type",
 | 
			
		||||
@@ -85,17 +90,26 @@ const handleStatusChange = ({ filed, value, record, reload }) => {
 | 
			
		||||
</script>
 | 
			
		||||
<template>
 | 
			
		||||
  <SimpleTable :columns="columns" :request="getList">
 | 
			
		||||
    <template #header="{ reload }">
 | 
			
		||||
      <a-space>
 | 
			
		||||
        <a-button @click="popup({ reload })" size="small" type="primary"
 | 
			
		||||
          ><template #icon> <icon-plus /> </template>新增
 | 
			
		||||
        </a-button>
 | 
			
		||||
        <a-button type="primary" status="success" href="https://gpt.bemore.lol" target="_blank">
 | 
			
		||||
          <template #icon>
 | 
			
		||||
            <icon-link />
 | 
			
		||||
          </template>
 | 
			
		||||
          购买API-KEY
 | 
			
		||||
        </a-button>
 | 
			
		||||
      </a-space>
 | 
			
		||||
    </template>
 | 
			
		||||
    <template #action="{ record, reload }">
 | 
			
		||||
      <a-link @click="popup({ record, reload })">编辑</a-link>
 | 
			
		||||
      <a-popconfirm content="确定删除?" @ok="handleDelete(record, reload)">
 | 
			
		||||
        <a-link status="danger">删除</a-link>
 | 
			
		||||
      </a-popconfirm>
 | 
			
		||||
    </template>
 | 
			
		||||
    <template #header="{ reload }">
 | 
			
		||||
      <a-button @click="popup({ reload })" size="small" type="primary"
 | 
			
		||||
        ><template #icon> <icon-plus /> </template>新增
 | 
			
		||||
      </a-button>
 | 
			
		||||
    </template>
 | 
			
		||||
 | 
			
		||||
    <template #value="{ record, column }">
 | 
			
		||||
      <a-typography-text copyable ellipsis style="margin: 0">
 | 
			
		||||
        {{ record[column.dataIndex] }}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,8 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <a-alert type="warning">
 | 
			
		||||
    <div class="warning">
 | 
			
		||||
      {{
 | 
			
		||||
        `注意:如果是百度文心一言平台,API-KEY 为 APIKey|SecretKey,中间用竖线(|)连接\n注意:如果是讯飞星火大模型,API-KEY 为 AppId|APIKey|APISecret,中间用竖线(|)连接`
 | 
			
		||||
      }}
 | 
			
		||||
      <div>注意:如果是百度文心一言平台,API-KEY 为 APIKey|SecretKey,中间用竖线(|)连接</div>
 | 
			
		||||
      <div>注意:如果是讯飞星火大模型,API-KEY 为 AppId|APIKey|APISecret,中间用竖线(|)连接</div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </a-alert>
 | 
			
		||||
  <a-form
 | 
			
		||||
@@ -18,7 +17,12 @@
 | 
			
		||||
      :rules="[{ required: true, message: '请输入所属平台' }]"
 | 
			
		||||
      :validate-trigger="['change', 'input']"
 | 
			
		||||
    >
 | 
			
		||||
      <a-input v-model="form.platform" placeholder="请输入所属平台" />
 | 
			
		||||
      <a-select
 | 
			
		||||
        v-model="form.platform"
 | 
			
		||||
        placeholder="请输入所属平台"
 | 
			
		||||
        :options="platformOptions"
 | 
			
		||||
        @change="handlePlatformChange"
 | 
			
		||||
      />
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
    <a-form-item
 | 
			
		||||
      field="name"
 | 
			
		||||
@@ -35,7 +39,12 @@
 | 
			
		||||
      :rules="[{ required: true, message: '请输入用途' }]"
 | 
			
		||||
      :validate-trigger="['change', 'input']"
 | 
			
		||||
    >
 | 
			
		||||
      <a-select v-model="form.type" placeholder="请输入用途" :options="typeOPtions"> </a-select>
 | 
			
		||||
      <a-select
 | 
			
		||||
        v-model="form.type"
 | 
			
		||||
        placeholder="请输入用途"
 | 
			
		||||
        :options="typeOptions"
 | 
			
		||||
        @change="handlePlatformChange"
 | 
			
		||||
      />
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
    <a-form-item
 | 
			
		||||
      field="value"
 | 
			
		||||
@@ -55,13 +64,15 @@
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
 | 
			
		||||
    <a-form-item field="use_proxy" label="使用代理">
 | 
			
		||||
      <a-switch v-model="form.use_proxy" />
 | 
			
		||||
      <a-tooltip
 | 
			
		||||
        content="是否使用代理访问 API URL,OpenAI 官方API需要开启代理访问"
 | 
			
		||||
        position="right"
 | 
			
		||||
      >
 | 
			
		||||
        <icon-info-circle-fill />
 | 
			
		||||
      </a-tooltip>
 | 
			
		||||
      <a-space>
 | 
			
		||||
        <a-switch v-model="form.use_proxy" />
 | 
			
		||||
        <a-tooltip
 | 
			
		||||
          content="是否使用代理访问 API URL,OpenAI 官方API需要开启代理访问"
 | 
			
		||||
          position="right"
 | 
			
		||||
        >
 | 
			
		||||
          <icon-info-circle-fill />
 | 
			
		||||
        </a-tooltip>
 | 
			
		||||
      </a-space>
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
    <a-form-item field="enable" label="启用状态">
 | 
			
		||||
      <a-switch v-model="form.enable" />
 | 
			
		||||
@@ -86,7 +97,7 @@ defineExpose({
 | 
			
		||||
  form,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const typeOPtions = [
 | 
			
		||||
const typeOptions = [
 | 
			
		||||
  {
 | 
			
		||||
    label: "聊天",
 | 
			
		||||
    value: "chart",
 | 
			
		||||
@@ -96,6 +107,48 @@ const typeOPtions = [
 | 
			
		||||
    value: "img",
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const platformOptions = [
 | 
			
		||||
  {
 | 
			
		||||
    label: "【OpenAI】ChatGPT",
 | 
			
		||||
    value: "OpenAI",
 | 
			
		||||
    api_url: "https://gpt.bemore.lol/v1/chat/completions",
 | 
			
		||||
    img_url: "https://gpt.bemore.lol/v1/images/generations",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    label: "【讯飞】星火大模型",
 | 
			
		||||
    value: "XunFei",
 | 
			
		||||
    api_url: "wss://spark-api.xf-yun.com/{version}/chat",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    label: "【清华智普】ChatGLM",
 | 
			
		||||
    value: "ChatGLM",
 | 
			
		||||
    api_url: "https://open.bigmodel.cn/api/paas/v3/model-api/{model}/sse-invoke",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    label: "【百度】文心一言",
 | 
			
		||||
    value: "Baidu",
 | 
			
		||||
    api_url: "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/{model}",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    label: "【微软】Azure",
 | 
			
		||||
    value: "Azure",
 | 
			
		||||
    api_url:
 | 
			
		||||
      "https://chat-bot-api.openai.azure.com/openai/deployments/{model}/chat/completions?api-version=2023-05-15",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    label: "【阿里】千义通问",
 | 
			
		||||
    value: "QWen",
 | 
			
		||||
    api_url: "https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation",
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const handlePlatformChange = () => {
 | 
			
		||||
  const obj = platformOptions.find((item) => item.value === form.value.platform);
 | 
			
		||||
  if (obj) {
 | 
			
		||||
    form.value.api_url = form.value.type === "img" ? obj.img_url : obj.api_url;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="less" scoped>
 | 
			
		||||
.content-title {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,12 @@
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { getList, save, deleting, setStatus } from "./api";
 | 
			
		||||
import { ref } from "vue";
 | 
			
		||||
import ChatModelForm from "./ChatModelForm.vue";
 | 
			
		||||
import useCustomFormPopup from "@/composables/useCustomFormPopup";
 | 
			
		||||
import { Message } from "@arco-design/web-vue";
 | 
			
		||||
import { Message, type TableColumnData } from "@arco-design/web-vue";
 | 
			
		||||
import SimpleTable from "@/components/SimpleTable/SimpleTable.vue";
 | 
			
		||||
import { dateFormat } from "@chatgpt-plus/packages/utils";
 | 
			
		||||
// table 配置
 | 
			
		||||
const columns = [
 | 
			
		||||
const columns: TableColumnData[] = [
 | 
			
		||||
  {
 | 
			
		||||
    title: "所属平台",
 | 
			
		||||
    dataIndex: "platform",
 | 
			
		||||
@@ -49,20 +48,9 @@ const columns = [
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
// 数据
 | 
			
		||||
const tableData = ref([]);
 | 
			
		||||
const getData = () => {
 | 
			
		||||
  getList().then(({ code, data }) => {
 | 
			
		||||
    if (code === 0) {
 | 
			
		||||
      tableData.value = data;
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
getData();
 | 
			
		||||
 | 
			
		||||
//  新增编辑
 | 
			
		||||
const popup = useCustomFormPopup(ChatModelForm, save, {
 | 
			
		||||
  popupProps: (arg) => ({ title: arg[0].record ? "编辑ApiKey" : "新增ApiKey" }),
 | 
			
		||||
  popupProps: (arg) => ({ title: arg[0].record ? "编辑模型" : "新增模型" }),
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 删除
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@
 | 
			
		||||
      :rules="[{ required: true, message: '请输入所属平台' }]"
 | 
			
		||||
      :validate-trigger="['change', 'input']"
 | 
			
		||||
    >
 | 
			
		||||
      <a-input v-model="form.platform" placeholder="请输入所属平台" />
 | 
			
		||||
      <a-select v-model="form.platform" placeholder="请输入所属平台" :options="platformOptions" />
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
    <a-form-item
 | 
			
		||||
      field="name"
 | 
			
		||||
@@ -33,10 +33,12 @@
 | 
			
		||||
      :validate-trigger="['change', 'input']"
 | 
			
		||||
      showable
 | 
			
		||||
    >
 | 
			
		||||
      <a-input-number v-model="form.weight" placeholder="请输入对话权重" />
 | 
			
		||||
      <a-tooltip content="对话权重,每次对话扣减多少次对话额度" position="right">
 | 
			
		||||
        <icon-info-circle-fill />
 | 
			
		||||
      </a-tooltip>
 | 
			
		||||
      <a-space>
 | 
			
		||||
        <a-input-number v-model="form.weight" placeholder="请输入对话权重" />
 | 
			
		||||
        <a-tooltip content="对话权重,每次对话扣减多少次对话额度" position="right">
 | 
			
		||||
          <icon-info-circle-fill />
 | 
			
		||||
        </a-tooltip>
 | 
			
		||||
      </a-space>
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
 | 
			
		||||
    <a-form-item field="open" label="开放状态代理">
 | 
			
		||||
@@ -65,15 +67,13 @@ defineExpose({
 | 
			
		||||
  form,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const typeOPtions = [
 | 
			
		||||
  {
 | 
			
		||||
    label: "聊天",
 | 
			
		||||
    value: "chart",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    label: "绘图",
 | 
			
		||||
    value: "img",
 | 
			
		||||
  },
 | 
			
		||||
const platformOptions = [
 | 
			
		||||
  { label: "【OpenAI】ChatGPT", value: "OpenAI" },
 | 
			
		||||
  { label: "【讯飞】星火大模型", value: "XunFei" },
 | 
			
		||||
  { label: "【清华智普】ChatGLM", value: "ChatGLM" },
 | 
			
		||||
  { label: "【百度】文心一言", value: "Baidu" },
 | 
			
		||||
  { label: "【微软】Azure", value: "Azure" },
 | 
			
		||||
  { label: "【阿里】通义千问", value: "QWen" },
 | 
			
		||||
];
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="less" scoped>
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@ import app from "@/main";
 | 
			
		||||
import { getList, message, remove } from "./api";
 | 
			
		||||
import ChatsLogs from "./ChatsLogs.vue";
 | 
			
		||||
 | 
			
		||||
const columns: SearchTableColumns[] = [
 | 
			
		||||
const chatColumns: SearchTableColumns[] = [
 | 
			
		||||
  {
 | 
			
		||||
    dataIndex: "user_id",
 | 
			
		||||
    title: "账户ID",
 | 
			
		||||
@@ -42,10 +42,48 @@ const columns: SearchTableColumns[] = [
 | 
			
		||||
    dataIndex: "token",
 | 
			
		||||
    title: "消耗算力",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    dataIndex: "created_at",
 | 
			
		||||
    title: "创建时间",
 | 
			
		||||
    search: {
 | 
			
		||||
      valueType: "range",
 | 
			
		||||
    },
 | 
			
		||||
    render: ({ record }) => dateFormat(record.created_at),
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    title: "操作",
 | 
			
		||||
    fixed: "right",
 | 
			
		||||
    slotName: "actions",
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const messageColumns: SearchTableColumns[] = [
 | 
			
		||||
  {
 | 
			
		||||
    dataIndex: "user_id",
 | 
			
		||||
    title: "账户ID",
 | 
			
		||||
    search: {
 | 
			
		||||
      valueType: "input",
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    dataIndex: "username",
 | 
			
		||||
    title: "账户",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    dataIndex: "title",
 | 
			
		||||
    title: "标题",
 | 
			
		||||
    search: {
 | 
			
		||||
      valueType: "input",
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    dataIndex: "msg_num",
 | 
			
		||||
    title: "消息数量",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    dataIndex: "token",
 | 
			
		||||
    title: "消耗算力",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    dataIndex: "created_at",
 | 
			
		||||
    title: "创建时间",
 | 
			
		||||
@@ -62,8 +100,8 @@ const columns: SearchTableColumns[] = [
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const tabsList = [
 | 
			
		||||
  { key: "1", title: "对话列表", api: getList, columns },
 | 
			
		||||
  { key: "2", title: "消息记录", api: message, columns },
 | 
			
		||||
  { key: "1", title: "对话列表", api: getList, columns: chatColumns },
 | 
			
		||||
  { key: "2", title: "消息记录", api: message, columns: messageColumns },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const activeKey = ref(tabsList[0].key);
 | 
			
		||||
@@ -101,7 +139,7 @@ const handleCheck = (record) => {
 | 
			
		||||
            content="是否删除?"
 | 
			
		||||
            position="left"
 | 
			
		||||
            type="warning"
 | 
			
		||||
            :on-before-ok="() => handleRemove(record.id, reload)"
 | 
			
		||||
            :on-before-ok="() => handleRemove(record.chat_id, reload)"
 | 
			
		||||
          >
 | 
			
		||||
            <a-link status="danger">删除</a-link>
 | 
			
		||||
          </a-popconfirm>
 | 
			
		||||
 
 | 
			
		||||
@@ -8,10 +8,10 @@ export const getList = (data?: Record<string, unknown>) => {
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const remove = (data) => {
 | 
			
		||||
export const remove = (params) => {
 | 
			
		||||
  return http({
 | 
			
		||||
    url: "/api/admin/order/remove",
 | 
			
		||||
    method: "post",
 | 
			
		||||
    data
 | 
			
		||||
    method: "get",
 | 
			
		||||
    params
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
@@ -1,13 +1,12 @@
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { getList, save, deleting, setStatus } from "./api";
 | 
			
		||||
import { ref } from "vue";
 | 
			
		||||
import ProductForm from "./ProductForm.vue";
 | 
			
		||||
import useCustomFormPopup from "@/composables/useCustomFormPopup";
 | 
			
		||||
import { Message } from "@arco-design/web-vue";
 | 
			
		||||
import { Message, type TableColumnData } from "@arco-design/web-vue";
 | 
			
		||||
import SimpleTable from "@/components/SimpleTable/SimpleTable.vue";
 | 
			
		||||
import { dateFormat } from "@chatgpt-plus/packages/utils";
 | 
			
		||||
// table 配置
 | 
			
		||||
const columns = [
 | 
			
		||||
const columns: TableColumnData[] = [
 | 
			
		||||
  {
 | 
			
		||||
    title: "产品名称",
 | 
			
		||||
    dataIndex: "name",
 | 
			
		||||
@@ -59,17 +58,6 @@ const columns = [
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
// 数据
 | 
			
		||||
const tableData = ref([]);
 | 
			
		||||
const getData = () => {
 | 
			
		||||
  getList().then(({ code, data }) => {
 | 
			
		||||
    if (code === 0) {
 | 
			
		||||
      tableData.value = data;
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
getData();
 | 
			
		||||
 | 
			
		||||
//  新增编辑
 | 
			
		||||
const popup = useCustomFormPopup(ProductForm, save, {
 | 
			
		||||
  popupProps: (arg) => ({ title: arg[0].record ? "编辑产品" : "新增产品" }),
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { getList, save, deleting, setStatus } from "./api";
 | 
			
		||||
import { reactive, ref } from "vue";
 | 
			
		||||
import { reactive } from "vue";
 | 
			
		||||
import RoleForm from "./RoleForm.vue";
 | 
			
		||||
import useCustomFormPopup from "@/composables/useCustomFormPopup";
 | 
			
		||||
import { Message } from "@arco-design/web-vue";
 | 
			
		||||
@@ -40,17 +40,6 @@ const expandable = reactive({
 | 
			
		||||
  width: 50,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 数据
 | 
			
		||||
const tableData = ref([]);
 | 
			
		||||
const getData = () => {
 | 
			
		||||
  getList().then(({ code, data }) => {
 | 
			
		||||
    if (code === 0) {
 | 
			
		||||
      tableData.value = data;
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
getData();
 | 
			
		||||
 | 
			
		||||
//展开行table
 | 
			
		||||
const expandColumns = [
 | 
			
		||||
  {
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@
 | 
			
		||||
      :rules="[{ required: true, message: '请输入角色图标' }]"
 | 
			
		||||
      :validate-trigger="['change', 'input']"
 | 
			
		||||
    >
 | 
			
		||||
      <a-input v-model="form.icon" placeholder="请输入角色图标" />
 | 
			
		||||
      <CustomUploader v-model="form.icon" placeholder="请输入角色图标" />
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
    <a-form-item
 | 
			
		||||
      field="hello_msg"
 | 
			
		||||
@@ -67,6 +67,7 @@
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import { ref, defineExpose, defineProps } from "vue";
 | 
			
		||||
import CustomUploader from "@/components/CustomUploader.vue";
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  data: {},
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -1,34 +1,34 @@
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import {onMounted} from "vue";
 | 
			
		||||
import {Message} from "@arco-design/web-vue";
 | 
			
		||||
import { onMounted } from "vue";
 | 
			
		||||
import { Message } from "@arco-design/web-vue";
 | 
			
		||||
import CustomUploader from "@/components/CustomUploader.vue";
 | 
			
		||||
import useSubmit from "@/composables/useSubmit";
 | 
			
		||||
import useRequest from "@/composables/useRequest";
 | 
			
		||||
import {getConfig, modelList, save} from "./api";
 | 
			
		||||
import SystemUploader from "./SystemUploader.vue";
 | 
			
		||||
import { getConfig, modelList, save } from "./api";
 | 
			
		||||
 | 
			
		||||
const {formRef, formData: system, handleSubmit, submitting} = useSubmit({});
 | 
			
		||||
const { formRef, formData: system, handleSubmit, submitting } = useSubmit({});
 | 
			
		||||
 | 
			
		||||
const [getModelOptions, modelOptions, modelOptionsLoading] = useRequest(modelList);
 | 
			
		||||
 | 
			
		||||
const rules = {
 | 
			
		||||
  title: [{required: true, message: "请输入网站标题"}],
 | 
			
		||||
  admin_title: [{required: true, message: "请输入控制台标题"}],
 | 
			
		||||
  title: [{ required: true, message: "请输入网站标题" }],
 | 
			
		||||
  admin_title: [{ required: true, message: "请输入控制台标题" }],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const handleSave = async () => {
 | 
			
		||||
  await handleSubmit(
 | 
			
		||||
      () =>
 | 
			
		||||
          save({
 | 
			
		||||
            key: "system",
 | 
			
		||||
            config: system,
 | 
			
		||||
          }),
 | 
			
		||||
      {}
 | 
			
		||||
    () =>
 | 
			
		||||
      save({
 | 
			
		||||
        key: "system",
 | 
			
		||||
        config: system,
 | 
			
		||||
      }),
 | 
			
		||||
    {}
 | 
			
		||||
  );
 | 
			
		||||
  Message.success("保存成功");
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const reload = async () => {
 | 
			
		||||
  const {data} = await getConfig({key: "system"});
 | 
			
		||||
  const { data } = await getConfig({ key: "system" });
 | 
			
		||||
  data && Object.assign(system, data);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -41,56 +41,43 @@ onMounted(async () => {
 | 
			
		||||
  <a-card :bordered="false">
 | 
			
		||||
    <a-form ref="formRef" :model="system" :rules="rules" auto-label-width :disabled="submitting">
 | 
			
		||||
      <a-form-item label="网站标题" field="title">
 | 
			
		||||
        <a-input v-model="system['title']"/>
 | 
			
		||||
        <a-input v-model="system['title']" />
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
      <a-form-item label="控制台标题" field="admin_title">
 | 
			
		||||
        <a-input v-model="system['admin_title']"/>
 | 
			
		||||
        <a-input v-model="system['admin_title']" />
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
      <a-form-item label="网站Logo" field="logo">
 | 
			
		||||
        <SystemUploader v-model="system['logo']" placeholder="推荐图片宽高比为 1:1"/>
 | 
			
		||||
      <a-form-item label="注册赠送对话次数" field="user_init_calls">
 | 
			
		||||
        <a-input v-model.number="system['init_chat_calls']" placeholder="新用户注册赠送对话次数" />
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
      <a-form-item label="注册赠送算力" field="init_power">
 | 
			
		||||
        <a-input-number v-model="system['init_power']" placeholder="新用户注册赠送初始算力"/>
 | 
			
		||||
      <a-form-item label="注册赠送绘图次数" field="init_img_calls">
 | 
			
		||||
        <a-input v-model.number="system['init_img_calls']" placeholder="新用户注册赠送绘图次数" />
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
      <a-form-item label="邀请用户赠送算力" field="invite_power">
 | 
			
		||||
        <a-input-number
 | 
			
		||||
            v-model="system['invite_power']"
 | 
			
		||||
            placeholder="邀请新用户注册赠送算力"
 | 
			
		||||
      <a-form-item label="邀请赠送对话次数" field="invite_chat_calls">
 | 
			
		||||
        <a-input
 | 
			
		||||
          v-model.number="system['invite_chat_calls']"
 | 
			
		||||
          placeholder="邀请新用户注册赠送对话次数"
 | 
			
		||||
        />
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
 | 
			
		||||
      <a-form-item label="VIP每月赠送算力" field="vip_month_power">
 | 
			
		||||
        <a-input-number v-model="system['vip_month_power']" placeholder="VIP用户每月赠送算力"/>
 | 
			
		||||
      <a-form-item label="邀请赠送绘图次数" field="invite_img_calls">
 | 
			
		||||
        <a-input
 | 
			
		||||
          v-model.number="system['invite_img_calls']"
 | 
			
		||||
          placeholder="邀请新用户注册赠送绘图次数"
 | 
			
		||||
        />
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
      <a-form-item label="MJ绘画价格" field="mj_power">
 | 
			
		||||
        <a-space>
 | 
			
		||||
          <a-input-number v-model="system['mj_power']" placeholder=""/>
 | 
			
		||||
          <a-tooltip content="MidJourney 单次绘图消耗多少单位算力" position="right">
 | 
			
		||||
            <icon-info-circle-fill size="18"/>
 | 
			
		||||
          </a-tooltip>
 | 
			
		||||
        </a-space>
 | 
			
		||||
      <a-form-item label="VIP每月对话次数" field="vip_month_calls">
 | 
			
		||||
        <a-input v-model.number="system['vip_month_calls']" placeholder="VIP用户每月赠送对话次数" />
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
      <a-form-item label="SD绘画价格" field="sd_power">
 | 
			
		||||
        <a-space>
 | 
			
		||||
          <a-input-number v-model="system['sd_power']" placeholder=""/>
 | 
			
		||||
          <a-tooltip content="Stable-Diffusion 单次绘图消耗多少单位算力" position="right">
 | 
			
		||||
            <icon-info-circle-fill size="18"/>
 | 
			
		||||
          </a-tooltip>
 | 
			
		||||
        </a-space>
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
      <a-form-item label="DALL绘画价格" field="dall_power">
 | 
			
		||||
        <a-space>
 | 
			
		||||
          <a-input-number v-model="system['dall_power']" placeholder=""/>
 | 
			
		||||
          <a-tooltip content="DALL-E-3 单次绘图消耗多少单位算力" position="right">
 | 
			
		||||
            <icon-info-circle-fill size="18"/>
 | 
			
		||||
          </a-tooltip>
 | 
			
		||||
        </a-space>
 | 
			
		||||
      <a-form-item label="VIP每月绘图次数" field="vip_month_img_calls">
 | 
			
		||||
        <a-input
 | 
			
		||||
          v-model.number="system['vip_month_img_calls']"
 | 
			
		||||
          placeholder="VIP用户每月赠送绘图次数"
 | 
			
		||||
        />
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
      <a-form-item label="开放注册" field="enabled_register">
 | 
			
		||||
        <a-space>
 | 
			
		||||
          <a-switch v-model="system['enabled_register']"/>
 | 
			
		||||
          <a-switch v-model="system['enabled_register']" />
 | 
			
		||||
          <a-tooltip content="关闭注册之后只能通过管理后台添加用户" position="right">
 | 
			
		||||
            <icon-info-circle-fill size="18"/>
 | 
			
		||||
            <icon-info-circle-fill size="18" />
 | 
			
		||||
          </a-tooltip>
 | 
			
		||||
        </a-space>
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
@@ -102,58 +89,61 @@ onMounted(async () => {
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
      <a-form-item label="启用众筹功能" field="enabled_reward">
 | 
			
		||||
        <a-space>
 | 
			
		||||
          <a-switch v-model="system['enabled_reward']"/>
 | 
			
		||||
          <a-tooltip content="开启众筹功能允许用户使用个人微信收款码进行收款" position="right">
 | 
			
		||||
            <icon-info-circle-fill size="18"/>
 | 
			
		||||
          <a-switch v-model="system['enabled_reward']" />
 | 
			
		||||
          <a-tooltip content="如果关闭次功能将不在用户菜单显示众筹二维码" position="right">
 | 
			
		||||
            <icon-info-circle-fill size="18" />
 | 
			
		||||
          </a-tooltip>
 | 
			
		||||
        </a-space>
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
      <template v-if="system['enabled_reward']">
 | 
			
		||||
        <a-form-item label="众筹算力单价" field="power_price">
 | 
			
		||||
          <a-input-number v-model="system['power_price']" placeholder="单位算力价格,如1块10个单位算力,那便填写 0.1"/>
 | 
			
		||||
        <a-form-item label="单次对话价格" field="chat_call_price">
 | 
			
		||||
          <a-input v-model="system['chat_call_price']" placeholder="众筹金额跟对话次数的兑换比例" />
 | 
			
		||||
        </a-form-item>
 | 
			
		||||
        <a-form-item label="单次绘图价格" field="img_call_price">
 | 
			
		||||
          <a-input v-model="system['img_call_price']" placeholder="众筹金额跟绘图次数的兑换比例" />
 | 
			
		||||
        </a-form-item>
 | 
			
		||||
        <a-form-item label="收款二维码" field="reward_img">
 | 
			
		||||
          <SystemUploader v-model="system['reward_img']" placeholder="众筹收款二维码地址"/>
 | 
			
		||||
          <CustomUploader v-model="system['reward_img']" placeholder="众筹收款二维码地址" />
 | 
			
		||||
        </a-form-item>
 | 
			
		||||
      </template>
 | 
			
		||||
      <a-form-item label="微信客服二维码" field="wechat_card_url">
 | 
			
		||||
        <SystemUploader v-model="system['wechat_card_url']" placeholder="微信客服二维码"/>
 | 
			
		||||
        <CustomUploader v-model="system['wechat_card_url']" placeholder="微信客服二维码" />
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
      <a-form-item label="订单超时时间" field="order_pay_timeout">
 | 
			
		||||
        <a-space style="width: 100%">
 | 
			
		||||
          <a-input-number
 | 
			
		||||
              v-model="system['order_pay_timeout']"
 | 
			
		||||
              placeholder="单位:秒"
 | 
			
		||||
              style="width: 100%"
 | 
			
		||||
          <a-input
 | 
			
		||||
            v-model.number="system['order_pay_timeout']"
 | 
			
		||||
            placeholder="单位:秒"
 | 
			
		||||
            style="width: 100%"
 | 
			
		||||
          />
 | 
			
		||||
          <a-tooltip position="right">
 | 
			
		||||
            <icon-info-circle-fill size="18"/>
 | 
			
		||||
            <template #content> 系统会定期清理超时未支付的订单<br/>默认值:900秒</template>
 | 
			
		||||
            <icon-info-circle-fill size="18" />
 | 
			
		||||
            <template #content> 系统会定期清理超时未支付的订单<br />默认值:900秒 </template>
 | 
			
		||||
          </a-tooltip>
 | 
			
		||||
        </a-space>
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
      <a-form-item label="会员充值说明" field="order_pay_info_text">
 | 
			
		||||
        <a-textarea
 | 
			
		||||
            v-model="system['order_pay_info_text']"
 | 
			
		||||
            :autosize="{ minRows: 3, maxRows: 10 }"
 | 
			
		||||
            placeholder="请输入会员充值说明文字,比如介绍会员计划"
 | 
			
		||||
          v-model="system['order_pay_info_text']"
 | 
			
		||||
          :autosize="{ minRows: 3, maxRows: 10 }"
 | 
			
		||||
          placeholder="请输入会员充值说明文字,比如介绍会员计划"
 | 
			
		||||
        />
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
      <a-form-item label="默认AI模型" field="default_models">
 | 
			
		||||
        <a-space style="width: 100%">
 | 
			
		||||
          <a-select
 | 
			
		||||
              v-model="system['default_models']"
 | 
			
		||||
              multiple
 | 
			
		||||
              :filterable="true"
 | 
			
		||||
              placeholder="选择AI模型,多选"
 | 
			
		||||
              :options="modelOptions"
 | 
			
		||||
              :loading="modelOptionsLoading"
 | 
			
		||||
              :field-names="{ value: 'value', label: 'name' }"
 | 
			
		||||
              style="width: 100%"
 | 
			
		||||
            v-model="system['default_models']"
 | 
			
		||||
            multiple
 | 
			
		||||
            :filterable="true"
 | 
			
		||||
            placeholder="选择AI模型,多选"
 | 
			
		||||
            :options="modelOptions"
 | 
			
		||||
            :loading="modelOptionsLoading"
 | 
			
		||||
            :field-names="{ value: 'value', label: 'name' }"
 | 
			
		||||
            style="width: 100%"
 | 
			
		||||
          >
 | 
			
		||||
          </a-select>
 | 
			
		||||
          <a-tooltip content="新用户注册默认开通的 AI 模型" position="right">
 | 
			
		||||
            <icon-info-circle-fill size="18"/>
 | 
			
		||||
            <icon-info-circle-fill size="18" />
 | 
			
		||||
          </a-tooltip>
 | 
			
		||||
        </a-space>
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,11 @@ import { dateFormat } from "@chatgpt-plus/packages/utils";
 | 
			
		||||
import UserPassword from "./UserPassword.vue";
 | 
			
		||||
import useCustomFormPopup from "@/composables/useCustomFormPopup";
 | 
			
		||||
const columns: SearchTableColumns[] = [
 | 
			
		||||
  {
 | 
			
		||||
    title: "用户头像",
 | 
			
		||||
    dataIndex: "avatar",
 | 
			
		||||
    slotName: "avatar",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    title: "账号",
 | 
			
		||||
    dataIndex: "username",
 | 
			
		||||
@@ -16,17 +21,9 @@ const columns: SearchTableColumns[] = [
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    title: "剩余对话次数",
 | 
			
		||||
    title: "剩余算力",
 | 
			
		||||
    dataIndex: "calls",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    title: "剩余绘图次数",
 | 
			
		||||
    dataIndex: "img_calls",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    title: "累计消耗tokens",
 | 
			
		||||
    dataIndex: "total_tokens",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    title: "状态",
 | 
			
		||||
    dataIndex: "status",
 | 
			
		||||
@@ -38,9 +35,7 @@ const columns: SearchTableColumns[] = [
 | 
			
		||||
    title: "过期时间",
 | 
			
		||||
    dataIndex: "expired_time",
 | 
			
		||||
    width: 180,
 | 
			
		||||
    render: ({ record }) => {
 | 
			
		||||
      return dateFormat(record.expired_time);
 | 
			
		||||
    },
 | 
			
		||||
    slotName: "expired_time",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    title: "注册时间",
 | 
			
		||||
@@ -76,6 +71,15 @@ const handleDelete = async ({ id }: { id: string }, reload) => {
 | 
			
		||||
</script>
 | 
			
		||||
<template>
 | 
			
		||||
  <SearchTable :request="getList" :columns="columns">
 | 
			
		||||
    <template #avatar="{ record }">
 | 
			
		||||
      <a-avatar>
 | 
			
		||||
        <a-image :src="record.avatar" style="border-radius: 50%" />
 | 
			
		||||
      </a-avatar>
 | 
			
		||||
    </template>
 | 
			
		||||
    <template #expired_time="{ record }">
 | 
			
		||||
      <a-tag v-if="record.expired_time === 0" color="blue">长期有效</a-tag>
 | 
			
		||||
      <template v-else>{{ dateFormat(record.expired_time) }}</template>
 | 
			
		||||
    </template>
 | 
			
		||||
    <template #actions="{ record, reload }">
 | 
			
		||||
      <a-link @click="editModal({ record, reload })">编辑</a-link>
 | 
			
		||||
      <a-popconfirm content="确定删除?" @ok="handleDelete(record, reload)">
 | 
			
		||||
 
 | 
			
		||||
@@ -20,17 +20,10 @@
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
    <a-form-item
 | 
			
		||||
      field="calls"
 | 
			
		||||
      label="对话次数"
 | 
			
		||||
      :rules="[{ required: true, message: '请输入对话次数' }]"
 | 
			
		||||
      label="用户算力"
 | 
			
		||||
      :rules="[{ required: true, message: '请输入用户算力' }]"
 | 
			
		||||
    >
 | 
			
		||||
      <a-input-number v-model="form.calls" placeholder="请输入对话次数" />
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
    <a-form-item
 | 
			
		||||
      field="img_calls"
 | 
			
		||||
      label="绘图次数"
 | 
			
		||||
      :rules="[{ required: true, message: '请输入绘图次数' }]"
 | 
			
		||||
    >
 | 
			
		||||
      <a-input-number v-model="form.img_calls" placeholder="请输入绘图次数" />
 | 
			
		||||
      <a-input-number v-model="form.calls" placeholder="请输入用户算力" />
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
    <a-form-item field="expired_time" label="有效期">
 | 
			
		||||
      <a-date-picker v-model="form.expired_time" placeholder="请选择有效期" />
 | 
			
		||||
 
 | 
			
		||||
@@ -2,137 +2,115 @@ html,
 | 
			
		||||
body,
 | 
			
		||||
#app,
 | 
			
		||||
.wrapper {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
body {
 | 
			
		||||
    font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
 | 
			
		||||
    -webkit-font-smoothing: antialiased;
 | 
			
		||||
    text-rendering: optimizeLegibility;
 | 
			
		||||
  font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
 | 
			
		||||
  -webkit-font-smoothing: antialiased;
 | 
			
		||||
  text-rendering: optimizeLegibility;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.admin-home a {
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
  text-decoration: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.admin-home .content-box {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    left: 250px;
 | 
			
		||||
    right: 0;
 | 
			
		||||
    top: 0;
 | 
			
		||||
    bottom: 0;
 | 
			
		||||
    /*padding-bottom: 30px;*/
 | 
			
		||||
    -webkit-transition: left 0.3s ease-in-out;
 | 
			
		||||
    transition: left 0.3s ease-in-out;
 | 
			
		||||
    background: #f0f0f0;
 | 
			
		||||
    overflow-y: scroll;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  left: 250px;
 | 
			
		||||
  right: 0;
 | 
			
		||||
  top: 0;
 | 
			
		||||
  bottom: 0;
 | 
			
		||||
  padding-bottom: 30px;
 | 
			
		||||
  -webkit-transition: left 0.3s ease-in-out;
 | 
			
		||||
  transition: left 0.3s ease-in-out;
 | 
			
		||||
  background: #f0f0f0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.admin-home .content-box .content {
 | 
			
		||||
    width: auto;
 | 
			
		||||
    padding: 10px;
 | 
			
		||||
    box-sizing: border-box;
 | 
			
		||||
    /*BaseForm*/
 | 
			
		||||
  width: auto;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  padding: 10px;
 | 
			
		||||
  overflow-y: scroll;
 | 
			
		||||
  box-sizing: border-box;
 | 
			
		||||
/*BaseForm*/
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.admin-home .content-box .content .container {
 | 
			
		||||
    padding: 30px;
 | 
			
		||||
    background: #fff;
 | 
			
		||||
    border: 1px solid #ddd;
 | 
			
		||||
    border-radius: 5px;
 | 
			
		||||
  padding: 30px;
 | 
			
		||||
  background: #fff;
 | 
			
		||||
  border: 1px solid #ddd;
 | 
			
		||||
  border-radius: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.admin-home .content-box .content .container .handle-box {
 | 
			
		||||
    margin-bottom: 20px;
 | 
			
		||||
  margin-bottom: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.admin-home .content-box .content .crumbs {
 | 
			
		||||
    margin: 10px 0;
 | 
			
		||||
  margin: 10px 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.admin-home .content-box .content .el-table th {
 | 
			
		||||
    background-color: #f5f7fa !important;
 | 
			
		||||
  background-color: #f5f7fa !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.admin-home .content-box .content .pagination {
 | 
			
		||||
    margin: 20px 0;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
  margin: 20px 0;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.admin-home .content-box .content .plugins-tips {
 | 
			
		||||
    padding: 20px 10px;
 | 
			
		||||
    margin-bottom: 20px;
 | 
			
		||||
  padding: 20px 10px;
 | 
			
		||||
  margin-bottom: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.admin-home .content-box .content .el-button + .el-tooltip {
 | 
			
		||||
    margin-left: 10px;
 | 
			
		||||
  margin-left: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.admin-home .content-box .content .el-table tr:hover {
 | 
			
		||||
    background: #f6faff;
 | 
			
		||||
  background: #f6faff;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.admin-home .content-box .content .mgb20 {
 | 
			
		||||
    margin-bottom: 20px;
 | 
			
		||||
  margin-bottom: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.admin-home .content-box .content .move-enter-active,
 | 
			
		||||
.admin-home .content-box .content .move-leave-active {
 | 
			
		||||
    transition: opacity 0.1s ease;
 | 
			
		||||
  transition: opacity 0.1s ease;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.admin-home .content-box .content .move-enter-from,
 | 
			
		||||
.admin-home .content-box .content .move-leave-to {
 | 
			
		||||
    opacity: 0;
 | 
			
		||||
  opacity: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.admin-home .content-box .content .form-box {
 | 
			
		||||
    width: 600px;
 | 
			
		||||
  width: 600px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.admin-home .content-box .content .form-box .line {
 | 
			
		||||
    text-align: center;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.admin-home .content-box .content .el-time-panel__content::after,
 | 
			
		||||
.admin-home .content-box .content .el-time-panel__content::before {
 | 
			
		||||
    margin-top: -7px;
 | 
			
		||||
  margin-top: -7px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.admin-home .content-box .content .el-time-spinner__wrapper .el-scrollbar__wrap:not(.el-scrollbar__wrap--hidden-default) {
 | 
			
		||||
    padding-bottom: 0;
 | 
			
		||||
  padding-bottom: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.admin-home .content-box .content [class*=" el-icon-"],
 | 
			
		||||
.admin-home .content-box .content [class^=el-icon-] {
 | 
			
		||||
    speak: none;
 | 
			
		||||
    font-style: normal;
 | 
			
		||||
    font-weight: 400;
 | 
			
		||||
    font-variant: normal;
 | 
			
		||||
    text-transform: none;
 | 
			
		||||
    line-height: 1;
 | 
			
		||||
    vertical-align: baseline;
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    -webkit-font-smoothing: antialiased;
 | 
			
		||||
    -moz-osx-font-smoothing: grayscale;
 | 
			
		||||
  speak: none;
 | 
			
		||||
  font-style: normal;
 | 
			
		||||
  font-weight: 400;
 | 
			
		||||
  font-variant: normal;
 | 
			
		||||
  text-transform: none;
 | 
			
		||||
  line-height: 1;
 | 
			
		||||
  vertical-align: baseline;
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  -webkit-font-smoothing: antialiased;
 | 
			
		||||
  -moz-osx-font-smoothing: grayscale;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.admin-home .content-box .content .el-sub-menu [class^=el-icon-] {
 | 
			
		||||
    vertical-align: middle;
 | 
			
		||||
    margin-right: 5px;
 | 
			
		||||
    width: 24px;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    font-size: 18px;
 | 
			
		||||
  vertical-align: middle;
 | 
			
		||||
  margin-right: 5px;
 | 
			
		||||
  width: 24px;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
  font-size: 18px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.admin-home .content-box .content [hidden] {
 | 
			
		||||
    display: none !important;
 | 
			
		||||
  display: none !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.admin-home .content-collapse {
 | 
			
		||||
    left: 65px;
 | 
			
		||||
  left: 65px;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
VUE_APP_API_HOST=http://172.22.11.2:5678
 | 
			
		||||
VUE_APP_WS_HOST=ws://172.22.11.2:5678
 | 
			
		||||
VUE_APP_API_HOST=http://localhost:5678
 | 
			
		||||
VUE_APP_WS_HOST=ws://localhost:5678
 | 
			
		||||
VUE_APP_USER=18575670125
 | 
			
		||||
VUE_APP_PASS=12345678
 | 
			
		||||
VUE_APP_ADMIN_USER=admin
 | 
			
		||||
 
 | 
			
		||||
@@ -2,9 +2,10 @@
 | 
			
		||||
  display: flex;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
}
 | 
			
		||||
.el-form-item__content .tip-input .input {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
.el-form-item__content .tip-input .el-input {
 | 
			
		||||
  width: 50%;
 | 
			
		||||
}
 | 
			
		||||
.el-form-item__content .tip-input .info {
 | 
			
		||||
  margin-left: 6px;
 | 
			
		||||
  margin-top: 2px;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,12 +3,13 @@
 | 
			
		||||
    display flex
 | 
			
		||||
    width 100%
 | 
			
		||||
 | 
			
		||||
    .input {
 | 
			
		||||
      width 100%
 | 
			
		||||
    .el-input {
 | 
			
		||||
      width 50%
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .info {
 | 
			
		||||
      margin-left 6px
 | 
			
		||||
      margin-top 2px
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -342,14 +342,14 @@ onMounted(() => {
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    // 获取系统配置
 | 
			
		||||
    httpGet("/api/admin/config/get?key=system").then(res => {
 | 
			
		||||
    httpGet("/api/config/get?key=system").then(res => {
 | 
			
		||||
      title.value = res.data.title
 | 
			
		||||
    }).catch(e => {
 | 
			
		||||
      ElMessage.error("获取系统配置失败:" + e.message)
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    // 获取系统公告
 | 
			
		||||
    httpGet("/api/admin/config/get?key=notice").then(res => {
 | 
			
		||||
    httpGet("/api/config/get?key=notice").then(res => {
 | 
			
		||||
      notice.value = md.render(res.data['content'])
 | 
			
		||||
      const oldNotice = localStorage.getItem(noticeKey.value);
 | 
			
		||||
      // 如果公告有更新,则显示公告
 | 
			
		||||
 
 | 
			
		||||
@@ -129,7 +129,7 @@ onMounted(() => {
 | 
			
		||||
      ElMessage.error("获取邀请码失败:" + e.message)
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    httpGet("/api/admin/config/get?key=system").then(res => {
 | 
			
		||||
    httpGet("/api/config/get?key=system").then(res => {
 | 
			
		||||
      inviteChatCalls.value = res.data["invite_chat_calls"]
 | 
			
		||||
      inviteImgCalls.value = res.data["invite_img_calls"]
 | 
			
		||||
    }).catch(e => {
 | 
			
		||||
 
 | 
			
		||||
@@ -227,7 +227,7 @@ onMounted(() => {
 | 
			
		||||
    router.push("/login")
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  httpGet("/api/admin/config/get?key=system").then(res => {
 | 
			
		||||
  httpGet("/api/config/get?key=system").then(res => {
 | 
			
		||||
    rewardImg.value = res.data['reward_img']
 | 
			
		||||
    enableReward.value = res.data['enabled_reward']
 | 
			
		||||
    orderPayInfoText.value = res.data['order_pay_info_text']
 | 
			
		||||
 
 | 
			
		||||
@@ -142,7 +142,7 @@ const wxImg = ref("/images/wx.png")
 | 
			
		||||
const ways = []
 | 
			
		||||
const placeholder = ref("用户名:")
 | 
			
		||||
 | 
			
		||||
httpGet("/api/admin/config/get?key=system").then(res => {
 | 
			
		||||
httpGet("/api/config/get?key=system").then(res => {
 | 
			
		||||
  if (res.data) {
 | 
			
		||||
    const registerWays = res.data['register_ways']
 | 
			
		||||
    if (arrayContains(registerWays, "mobile")) {
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@
 | 
			
		||||
 | 
			
		||||
    <div class="handle-box">
 | 
			
		||||
      <el-button type="primary" :icon="Plus" @click="add">新增</el-button>
 | 
			
		||||
      <a href="https://gpt.bemore.lol" target="_blank" style="margin-left: 10px">
 | 
			
		||||
      <a href="https://api.chat-plus.net" target="_blank" style="margin-left: 10px">
 | 
			
		||||
        <el-button type="success" :icon="ShoppingCart" @click="add" plain>购买API-KEY</el-button>
 | 
			
		||||
      </a>
 | 
			
		||||
    </div>
 | 
			
		||||
@@ -12,13 +12,20 @@
 | 
			
		||||
      <el-table :data="items" :row-key="row => row.id" table-layout="auto">
 | 
			
		||||
        <el-table-column prop="platform" label="所属平台"/>
 | 
			
		||||
        <el-table-column prop="name" label="名称"/>
 | 
			
		||||
        <el-table-column prop="value" label="KEY">
 | 
			
		||||
        <el-table-column prop="value" label="API KEY">
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            <el-tooltip class="box-item"
 | 
			
		||||
                        effect="dark"
 | 
			
		||||
                        :content="scope.row.api_url"
 | 
			
		||||
                        placement="top">{{ scope.row.value }}
 | 
			
		||||
            </el-tooltip>
 | 
			
		||||
            <span>{{ substr(scope.row.value, 20) }}</span>
 | 
			
		||||
            <el-icon class="copy-key" :data-clipboard-text="scope.row.value">
 | 
			
		||||
              <DocumentCopy/>
 | 
			
		||||
            </el-icon>
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-table-column>
 | 
			
		||||
        <el-table-column prop="api_url" label="API URL">
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            <span>{{ substr(scope.row.api_url, 30) }}</span>
 | 
			
		||||
            <el-icon class="copy-key" :data-clipboard-text="scope.row.api_url">
 | 
			
		||||
              <DocumentCopy/>
 | 
			
		||||
            </el-icon>
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-table-column>
 | 
			
		||||
        <el-table-column prop="type" label="用途">
 | 
			
		||||
@@ -27,11 +34,7 @@
 | 
			
		||||
            <el-tag v-else-if="scope.row.type === 'img'" type="success">绘图</el-tag>
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-table-column>
 | 
			
		||||
        <el-table-column prop="use_proxy" label="使用代理">
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            <el-switch v-model="scope.row['use_proxy']" @change="set('use_proxy',scope.row)"/>
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-table-column>
 | 
			
		||||
        <el-table-column prop="proxy_url" label="代理地址"/>
 | 
			
		||||
 | 
			
		||||
        <el-table-column label="最后使用时间">
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
@@ -99,18 +102,8 @@
 | 
			
		||||
                    placeholder="如果你用了第三方的 API 中转,这里填写中转地址"/>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
 | 
			
		||||
        <el-form-item label="使用代理:" prop="use_proxy">
 | 
			
		||||
          <el-switch v-model="item.use_proxy"/>
 | 
			
		||||
          <el-tooltip
 | 
			
		||||
              effect="dark"
 | 
			
		||||
              content="是否使用代理访问 API URL,OpenAI 官方API需要开启代理访问"
 | 
			
		||||
              raw-content
 | 
			
		||||
              placement="right"
 | 
			
		||||
          >
 | 
			
		||||
            <el-icon>
 | 
			
		||||
              <InfoFilled/>
 | 
			
		||||
            </el-icon>
 | 
			
		||||
          </el-tooltip>
 | 
			
		||||
        <el-form-item label="代理地址:" prop="proxy_url">
 | 
			
		||||
          <el-input v-model="item.proxy_url" autocomplete="off"/>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
 | 
			
		||||
        <el-form-item label="启用状态:" prop="enable">
 | 
			
		||||
@@ -129,11 +122,12 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {reactive, ref} from "vue";
 | 
			
		||||
import {onMounted, onUnmounted, reactive, ref} from "vue";
 | 
			
		||||
import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import {dateFormat, disabledDate, removeArrayItem} from "@/utils/libs";
 | 
			
		||||
import {InfoFilled, Plus, ShoppingCart} from "@element-plus/icons-vue";
 | 
			
		||||
import {dateFormat, disabledDate, removeArrayItem, substr} from "@/utils/libs";
 | 
			
		||||
import {DocumentCopy, InfoFilled, Plus, ShoppingCart} from "@element-plus/icons-vue";
 | 
			
		||||
import ClipboardJS from "clipboard";
 | 
			
		||||
 | 
			
		||||
// 变量定义
 | 
			
		||||
const items = ref([])
 | 
			
		||||
@@ -150,10 +144,10 @@ const formRef = ref(null)
 | 
			
		||||
const title = ref("")
 | 
			
		||||
const platforms = ref([
 | 
			
		||||
  {
 | 
			
		||||
    name: "【OpenAI】ChatGPT",
 | 
			
		||||
    name: "【OpenAI/中转】ChatGPT",
 | 
			
		||||
    value: "OpenAI",
 | 
			
		||||
    api_url: "https://gpt.bemore.lol/v1/chat/completions",
 | 
			
		||||
    img_url: "https://gpt.bemore.lol/v1/images/generations"
 | 
			
		||||
    api_url: "https://api.chat-plus.net/v1/chat/completions",
 | 
			
		||||
    img_url: "https://api.chat-plus.net/v1/images/generations"
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    name: "【讯飞】星火大模型",
 | 
			
		||||
@@ -186,6 +180,23 @@ const types = ref([
 | 
			
		||||
  {name: "绘画", value: "img"},
 | 
			
		||||
])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const clipboard = ref(null)
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  clipboard.value = new ClipboardJS('.copy-key');
 | 
			
		||||
  clipboard.value.on('success', () => {
 | 
			
		||||
    ElMessage.success('复制成功!');
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  clipboard.value.on('error', () => {
 | 
			
		||||
    ElMessage.error('复制失败!');
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
onUnmounted(() => {
 | 
			
		||||
  clipboard.value.destroy()
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
// 获取数据
 | 
			
		||||
httpGet('/api/admin/apikey/list').then((res) => {
 | 
			
		||||
  if (res.data) {
 | 
			
		||||
@@ -269,7 +280,6 @@ const changePlatform = () => {
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
@@ -285,6 +295,11 @@ const changePlatform = () => {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .copy-key {
 | 
			
		||||
    margin-left 5px
 | 
			
		||||
    cursor pointer
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .el-select {
 | 
			
		||||
    width: 100%
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -25,8 +25,18 @@
 | 
			
		||||
          <el-table :data="data.chat.items" :row-key="row => row.id" table-layout="auto">
 | 
			
		||||
            <el-table-column prop="user_id" label="账户ID"/>
 | 
			
		||||
            <el-table-column prop="username" label="账户"/>
 | 
			
		||||
            <el-table-column prop="title" label="标题"/>
 | 
			
		||||
            <el-table-column label="图标">
 | 
			
		||||
              <template #default="scope">
 | 
			
		||||
                <el-avatar :size="30" :src="scope.row.role.icon"/>
 | 
			
		||||
              </template>
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
            <el-table-column label="角色">
 | 
			
		||||
              <template #default="scope">
 | 
			
		||||
                <span>{{ scope.row.role.name }}</span>
 | 
			
		||||
              </template>
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
            <el-table-column prop="model" label="模型"/>
 | 
			
		||||
            <el-table-column prop="title" label="标题"/>
 | 
			
		||||
            <el-table-column prop="msg_num" label="消息数量"/>
 | 
			
		||||
            <el-table-column prop="token" label="消耗算力"/>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,10 @@
 | 
			
		||||
        </el-table-column>
 | 
			
		||||
        <el-table-column prop="name" label="模型名称"/>
 | 
			
		||||
        <el-table-column prop="value" label="模型值"/>
 | 
			
		||||
        <el-table-column prop="weight" label="对话权重"/>
 | 
			
		||||
        <el-table-column prop="power" label="费率"/>
 | 
			
		||||
        <el-table-column prop="max_tokens" label="最大响应长度"/>
 | 
			
		||||
        <el-table-column prop="max_context" label="最大上下文长度"/>
 | 
			
		||||
        <el-table-column prop="temperature" label="创意度"/>
 | 
			
		||||
        <el-table-column prop="enabled" label="启用状态">
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            <el-switch v-model="scope.row['enabled']" @change="modelSet('enabled',scope.row)"/>
 | 
			
		||||
@@ -69,16 +72,15 @@
 | 
			
		||||
          <el-input v-model="item.value" autocomplete="off"/>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
 | 
			
		||||
        <el-form-item label="对话权重:" prop="weight">
 | 
			
		||||
 | 
			
		||||
        <el-form-item label="费率:" prop="weight">
 | 
			
		||||
          <template #default>
 | 
			
		||||
            <div class="tip-input">
 | 
			
		||||
              <el-input-number :min="1" v-model="item.weight" autocomplete="off"/>
 | 
			
		||||
              <el-input-number :min="1" v-model="item.power" autocomplete="off"/>
 | 
			
		||||
              <div class="info">
 | 
			
		||||
                <el-tooltip
 | 
			
		||||
                    class="box-item"
 | 
			
		||||
                    effect="dark"
 | 
			
		||||
                    content="对话权重,每次对话扣减多少次对话额度"
 | 
			
		||||
                    content="每次对话扣减多少单位算力"
 | 
			
		||||
                    placement="right"
 | 
			
		||||
                >
 | 
			
		||||
                  <el-icon>
 | 
			
		||||
@@ -90,11 +92,78 @@
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
 | 
			
		||||
        <el-form-item label="最长响应:" prop="max_tokens">
 | 
			
		||||
          <el-input v-model.number="item.max_tokens" autocomplete="off" placeholder="模型最大响应长度"/>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
 | 
			
		||||
        <el-form-item label="最大上下文:" prop="max_context">
 | 
			
		||||
          <div class="tip-input">
 | 
			
		||||
            <el-input v-model.number="item.max_context" autocomplete="off" placeholder="模型最大上下文长度"/>
 | 
			
		||||
            <div class="info">
 | 
			
		||||
              <el-tooltip
 | 
			
		||||
                  class="box-item"
 | 
			
		||||
                  effect="dark"
 | 
			
		||||
                  raw-content
 | 
			
		||||
                  content="gpt-3.5-turbo:4096 <br/>
 | 
			
		||||
              gpt-3.5-turbo-16k: 16384 <br/>
 | 
			
		||||
              gpt-4:             8192 <br/>
 | 
			
		||||
              gpt-4-32k:         32768 <br/>
 | 
			
		||||
              chatglm_pro:       32768 <br/>
 | 
			
		||||
              chatglm_std:       16384 <br/>
 | 
			
		||||
              chatglm_lite:      4096 <br/>
 | 
			
		||||
              qwen-turbo:        8192 <br/>
 | 
			
		||||
              qwen-plus:         32768 <br/>
 | 
			
		||||
              文心一言:            8192 <br/>
 | 
			
		||||
              星火1.0:            4096 <br/>
 | 
			
		||||
              星火2.0-星火3.5:     8192"
 | 
			
		||||
                  placement="right"
 | 
			
		||||
              >
 | 
			
		||||
                <el-icon>
 | 
			
		||||
                  <InfoFilled/>
 | 
			
		||||
                </el-icon>
 | 
			
		||||
              </el-tooltip>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
 | 
			
		||||
        <el-form-item label="创意度:" prop="temperature">
 | 
			
		||||
          <div class="tip-input">
 | 
			
		||||
            <el-input v-model="item.temperature" autocomplete="off" placeholder="模型创意度"/>
 | 
			
		||||
            <div class="info">
 | 
			
		||||
              <el-tooltip
 | 
			
		||||
                  class="box-item"
 | 
			
		||||
                  effect="dark"
 | 
			
		||||
                  content="OpenAI 0-2,其他模型 0-1"
 | 
			
		||||
                  placement="right"
 | 
			
		||||
              >
 | 
			
		||||
                <el-icon>
 | 
			
		||||
                  <InfoFilled/>
 | 
			
		||||
                </el-icon>
 | 
			
		||||
              </el-tooltip>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
 | 
			
		||||
        <el-form-item label="启用状态:" prop="enable">
 | 
			
		||||
          <el-switch v-model="item.enabled"/>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="开放状态:" prop="open">
 | 
			
		||||
          <el-switch v-model="item.open"/>
 | 
			
		||||
          <div class="tip-input">
 | 
			
		||||
            <el-switch v-model="item.open"/>
 | 
			
		||||
            <div class="info">
 | 
			
		||||
              <el-tooltip
 | 
			
		||||
                  class="box-item"
 | 
			
		||||
                  effect="dark"
 | 
			
		||||
                  raw-content
 | 
			
		||||
                  content="开放后,该模型将对所有用户可见。<br/> 如果模型没有启用,则当前设置无效。"
 | 
			
		||||
                  placement="right"
 | 
			
		||||
              >
 | 
			
		||||
                <el-icon>
 | 
			
		||||
                  <InfoFilled/>
 | 
			
		||||
                </el-icon>
 | 
			
		||||
              </el-tooltip>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </el-form>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -28,9 +28,9 @@
 | 
			
		||||
        <el-table-column prop="username" label="下单用户"/>
 | 
			
		||||
        <el-table-column prop="subject" label="产品名称"/>
 | 
			
		||||
        <el-table-column prop="amount" label="订单金额"/>
 | 
			
		||||
        <el-table-column label="调用次数">
 | 
			
		||||
        <el-table-column label="充值算力">
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            <span>{{ scope.row.remark?.calls }}</span>
 | 
			
		||||
            <span>{{ scope.row.remark?.power }}</span>
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-table-column>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -20,8 +20,7 @@
 | 
			
		||||
            <span v-else>{{ scope.row.days }}</span>
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-table-column>
 | 
			
		||||
        <el-table-column prop="calls" label="对话次数"/>
 | 
			
		||||
        <el-table-column prop="img_calls" label="绘图次数"/>
 | 
			
		||||
        <el-table-column prop="power" label="算力"/>
 | 
			
		||||
        <el-table-column prop="sales" label="销量"/>
 | 
			
		||||
        <el-table-column prop="enabled" label="启用状态">
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
@@ -71,12 +70,8 @@
 | 
			
		||||
          <el-input v-model.number="item.days" autocomplete="off" placeholder="会员有效期(天)"/>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
 | 
			
		||||
        <el-form-item label="对话次数:" prop="calls">
 | 
			
		||||
          <el-input v-model.number="item.calls" autocomplete="off" placeholder="增加对话次数"/>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
 | 
			
		||||
        <el-form-item label="绘图次数:" prop="img_calls">
 | 
			
		||||
          <el-input v-model.number="item.img_calls" autocomplete="off" placeholder="增加绘图次数"/>
 | 
			
		||||
        <el-form-item label="算力:" prop="power">
 | 
			
		||||
          <el-input v-model.number="item.power" autocomplete="off" placeholder="增加算力值"/>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
 | 
			
		||||
        <el-form-item label="启用状态:" prop="enable">
 | 
			
		||||
 
 | 
			
		||||
@@ -11,23 +11,30 @@
 | 
			
		||||
            <el-form-item label="控制台标题" prop="admin_title">
 | 
			
		||||
              <el-input v-model="system['admin_title']"/>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
            <el-form-item label="注册赠送对话次数" prop="user_init_calls">
 | 
			
		||||
              <el-input v-model.number="system['init_chat_calls']" placeholder="新用户注册赠送对话次数"/>
 | 
			
		||||
            <el-form-item label="网站 LOGO" prop="logo">
 | 
			
		||||
              <el-input v-model="system['logo']" placeholder="网站LOGO图片">
 | 
			
		||||
                <template #append>
 | 
			
		||||
                  <el-upload
 | 
			
		||||
                      :auto-upload="true"
 | 
			
		||||
                      :show-file-list="false"
 | 
			
		||||
                      @click="beforeUpload('logo')"
 | 
			
		||||
                      :http-request="uploadImg"
 | 
			
		||||
                  >
 | 
			
		||||
                    <el-icon class="uploader-icon">
 | 
			
		||||
                      <UploadFilled/>
 | 
			
		||||
                    </el-icon>
 | 
			
		||||
                  </el-upload>
 | 
			
		||||
                </template>
 | 
			
		||||
              </el-input>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
            <el-form-item label="注册赠送绘图次数" prop="init_img_calls">
 | 
			
		||||
              <el-input v-model.number="system['init_img_calls']" placeholder="新用户注册赠送绘图次数"/>
 | 
			
		||||
            <el-form-item label="注册赠送算力" prop="init_power">
 | 
			
		||||
              <el-input v-model.number="system['init_power']" placeholder="新用户注册赠送算力"/>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
            <el-form-item label="邀请赠送对话次数" prop="invite_chat_calls">
 | 
			
		||||
              <el-input v-model.number="system['invite_chat_calls']" placeholder="邀请新用户注册赠送对话次数"/>
 | 
			
		||||
            <el-form-item label="邀请赠送算力" prop="invite_power">
 | 
			
		||||
              <el-input v-model.number="system['invite_power']" placeholder="邀请新用户注册赠送算力"/>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
            <el-form-item label="邀请赠送绘图次数" prop="invite_img_calls">
 | 
			
		||||
              <el-input v-model.number="system['invite_img_calls']" placeholder="邀请新用户注册赠送绘图次数"/>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
            <el-form-item label="VIP每月对话次数" prop="vip_month_calls">
 | 
			
		||||
              <el-input v-model.number="system['vip_month_calls']" placeholder="VIP用户每月赠送对话次数"/>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
            <el-form-item label="VIP每月绘图次数" prop="vip_month_img_calls">
 | 
			
		||||
              <el-input v-model.number="system['vip_month_img_calls']" placeholder="VIP用户每月赠送绘图次数"/>
 | 
			
		||||
            <el-form-item label="VIP每月赠送算力" prop="vip_month_power">
 | 
			
		||||
              <el-input v-model.number="system['vip_month_power']" placeholder="VIP用户每月赠送算力"/>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
 | 
			
		||||
            <el-form-item label="开放注册" prop="enabled_register">
 | 
			
		||||
@@ -66,11 +73,9 @@
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
 | 
			
		||||
            <div v-if="system['enabled_reward']">
 | 
			
		||||
              <el-form-item label="单次对话价格" prop="chat_call_price">
 | 
			
		||||
                <el-input v-model="system['chat_call_price']" placeholder="众筹金额跟对话次数的兑换比例"/>
 | 
			
		||||
              </el-form-item>
 | 
			
		||||
              <el-form-item label="单次绘图价格" prop="img_call_price">
 | 
			
		||||
                <el-input v-model="system['img_call_price']" placeholder="众筹金额跟绘图次数的兑换比例"/>
 | 
			
		||||
              <el-form-item label="算力单价" prop="power_price">
 | 
			
		||||
                <el-input v-model="system['power_price']"
 | 
			
		||||
                          placeholder="单位算力的价格,比如设置 0.1 表示捐赠1元钱可以得到10个单位算力"/>
 | 
			
		||||
              </el-form-item>
 | 
			
		||||
              <el-form-item label="收款二维码" prop="reward_img">
 | 
			
		||||
                <el-input v-model="system['reward_img']" placeholder="众筹收款二维码地址">
 | 
			
		||||
@@ -165,82 +170,21 @@
 | 
			
		||||
                </div>
 | 
			
		||||
              </template>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
            <el-form-item>
 | 
			
		||||
              <el-button type="primary" @click="save('system')">保存</el-button>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
          </el-form>
 | 
			
		||||
        </div>
 | 
			
		||||
      </el-tab-pane>
 | 
			
		||||
      <el-tab-pane label="模型配置" name="chat">
 | 
			
		||||
        <div class="container">
 | 
			
		||||
          <el-form :model="chat" label-position="right" label-width="150px" ref="chatFormRef" :rules="rules">
 | 
			
		||||
 | 
			
		||||
            <el-form-item label="开启聊天上下文">
 | 
			
		||||
              <el-switch v-model="chat['enable_context']"/>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
            <el-form-item label="保存聊天记录">
 | 
			
		||||
              <el-switch v-model="chat['enable_history']"/>
 | 
			
		||||
              <el-switch v-model="system['enable_context']"/>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
            <el-form-item label="会话上下文深度">
 | 
			
		||||
              <div style="width:100%">
 | 
			
		||||
                <el-input-number v-model="chat['context_deep']" :min="0" :max="10"/>
 | 
			
		||||
                <el-input-number v-model="system['context_deep']" :min="0" :max="10"/>
 | 
			
		||||
              </div>
 | 
			
		||||
              <div class="tip" style="margin-top: 10px; ">会话上下文深度:在老会话中继续会话,默认加载多少条聊天记录作为上下文。如果设置为
 | 
			
		||||
                0
 | 
			
		||||
                则不加载聊天记录,仅仅使用当前角色的上下文。该配置参数最好设置需要为偶数,否则将无法兼容百度的 API。
 | 
			
		||||
              </div>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
 | 
			
		||||
            <el-divider content-position="center">OpenAI</el-divider>
 | 
			
		||||
            <el-form-item label="模型创意度">
 | 
			
		||||
              <el-slider v-model="chat['open_ai']['temperature']" :max="2" :step="0.1"/>
 | 
			
		||||
              <div class="tip">值越大 AI 回答越发散,值越小回答越保守,建议保持默认值</div>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
            <el-form-item label="最大响应长度">
 | 
			
		||||
              <el-input v-model.number="chat['open_ai']['max_tokens']" placeholder="回复的最大字数,最大4096"/>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
 | 
			
		||||
            <el-divider content-position="center">Azure</el-divider>
 | 
			
		||||
            <el-form-item label="模型创意度">
 | 
			
		||||
              <el-slider v-model="chat['azure']['temperature']" :max="2" :step="0.1"/>
 | 
			
		||||
              <div class="tip">值越大 AI 回答越发散,值越小回答越保守,建议保持默认值</div>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
            <el-form-item label="最大响应长度">
 | 
			
		||||
              <el-input v-model.number="chat['azure']['max_tokens']" placeholder="回复的最大字数,最大4096"/>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
 | 
			
		||||
            <el-divider content-position="center">ChatGLM</el-divider>
 | 
			
		||||
            <el-form-item label="模型创意度">
 | 
			
		||||
              <el-slider v-model="chat['chat_gml']['temperature']" :max="1" :step="0.01"/>
 | 
			
		||||
              <div class="tip">值越大 AI 回答越发散,值越小回答越保守,建议保持默认值</div>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
            <el-form-item label="最大响应长度">
 | 
			
		||||
              <el-input v-model.number="chat['chat_gml']['max_tokens']" placeholder="回复的最大字数,最大4096"/>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
 | 
			
		||||
            <el-divider content-position="center">文心一言</el-divider>
 | 
			
		||||
            <el-form-item label="模型创意度">
 | 
			
		||||
              <el-slider v-model="chat['baidu']['temperature']" :max="1" :step="0.01"/>
 | 
			
		||||
              <div class="tip">值越大 AI 回答越发散,值越小回答越保守,建议保持默认值</div>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
            <el-form-item label="最大响应长度">
 | 
			
		||||
              <el-input v-model.number="chat['baidu']['max_tokens']" placeholder="回复的最大字数,最大4096"/>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
 | 
			
		||||
            <el-divider content-position="center">讯飞星火</el-divider>
 | 
			
		||||
            <el-form-item label="模型创意度">
 | 
			
		||||
              <el-slider v-model="chat['xun_fei']['temperature']" :max="1" :step="0.1"/>
 | 
			
		||||
              <div class="tip">值越大 AI 回答越发散,值越小回答越保守,建议保持默认值</div>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
            <el-form-item label="最大响应长度">
 | 
			
		||||
              <el-input v-model.number="chat['xun_fei']['max_tokens']" placeholder="回复的最大字数,最大4096"/>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
 | 
			
		||||
            <el-divider content-position="center">AI绘图</el-divider>
 | 
			
		||||
            <el-form-item label="DALL-E3出图数量">
 | 
			
		||||
              <el-input v-model.number="chat['dall_img_num']" placeholder="调用 DALL E3 API 传入的出图数量"/>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
            <el-form-item style="text-align: right">
 | 
			
		||||
              <el-button type="primary" @click="save('chat')">保存</el-button>
 | 
			
		||||
            <el-form-item>
 | 
			
		||||
              <el-button type="primary" @click="save('system')">保存</el-button>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
          </el-form>
 | 
			
		||||
        </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -17,9 +17,7 @@
 | 
			
		||||
            <el-image v-if="scope.row.vip" :src="vipImg" style="height: 20px;position: relative; top:5px; left: 5px"/>
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-table-column>
 | 
			
		||||
        <el-table-column prop="calls" label="剩余对话次数"/>
 | 
			
		||||
        <el-table-column prop="img_calls" label="剩余绘图次数"/>
 | 
			
		||||
        <el-table-column prop="total_tokens" label="累计消耗tokens"/>
 | 
			
		||||
        <el-table-column prop="power" label="剩余算力"/>
 | 
			
		||||
        <el-table-column label="状态" width="80">
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            <el-tag v-if="scope.row.status" type="success">正常</el-tag>
 | 
			
		||||
@@ -76,11 +74,8 @@
 | 
			
		||||
        <el-form-item v-if="add" label="密码:" prop="password">
 | 
			
		||||
          <el-input v-model="user.password" autocomplete="off"/>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="对话次数:" prop="calls">
 | 
			
		||||
          <el-input v-model.number="user.calls" autocomplete="off" placeholder="0"/>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="绘图次数:" prop="img_calls">
 | 
			
		||||
          <el-input v-model.number="user['img_calls']" autocomplete="off" placeholder="0"/>
 | 
			
		||||
        <el-form-item label="剩余算力:" prop="power">
 | 
			
		||||
          <el-input v-model.number="user.power" autocomplete="off" placeholder="0"/>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
 | 
			
		||||
        <el-form-item label="有效期:" prop="expired_time">
 | 
			
		||||
 
 | 
			
		||||
@@ -149,7 +149,7 @@ onMounted(() => {
 | 
			
		||||
    showFailToast("获取产品套餐失败:" + e.message)
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  httpGet("/api/admin/config/get?key=system").then(res => {
 | 
			
		||||
  httpGet("/api/config/get?key=system").then(res => {
 | 
			
		||||
    vipMonthCalls.value = res.data['vip_month_calls']
 | 
			
		||||
    vipMonthImgCalls.value = res.data['vip_month_img_calls']
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user