feat: 支持文心4.0模型,不同的用户可以订阅不同的AI模型
							
								
								
									
										41
									
								
								README.md
									
									
									
									
									
								
							
							
						
						@@ -4,11 +4,12 @@
 | 
				
			|||||||
ChatGLM,讯飞星火,文心一言等多个平台的大语言模型。集成了 MidJourney 和 Stable Diffusion AI绘画功能。主要有如下特性:
 | 
					ChatGLM,讯飞星火,文心一言等多个平台的大语言模型。集成了 MidJourney 和 Stable Diffusion AI绘画功能。主要有如下特性:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* 完整的开源系统,前端应用和后台管理系统皆可开箱即用。
 | 
					* 完整的开源系统,前端应用和后台管理系统皆可开箱即用。
 | 
				
			||||||
* 聊天体验跟 ChatGPT 官方版本完全一致。
 | 
					* 基于 Websocket 实现,完美的打字机体验。
 | 
				
			||||||
* 内置了各种预训练好的角色,比如小红书写手,英语翻译大师,苏格拉底,孔子,乔布斯,周报助手等。轻松满足你的各种聊天和应用需求。
 | 
					* 内置了各种预训练好的角色应用,比如小红书写手,英语翻译大师,苏格拉底,孔子,乔布斯,周报助手等。轻松满足你的各种聊天和应用需求。
 | 
				
			||||||
 | 
					* 支持 OPenAI,Azure,文心一言,讯飞星火,清华 ChatGLM等多个大语言模型。
 | 
				
			||||||
* 支持 MidJourney / Stable Diffusion AI 绘画集成,开箱即用。
 | 
					* 支持 MidJourney / Stable Diffusion AI 绘画集成,开箱即用。
 | 
				
			||||||
* 支持使用个人微信二维码作为充值收费的支付渠道,无需企业支付通道。(可定制开发其他支付通道支持)
 | 
					* 支持使用个人微信二维码作为充值收费的支付渠道,无需企业支付通道。(可定制开发其他支付通道支持)
 | 
				
			||||||
* 集成插件 API 功能,可结合 GPT 开发各种强大的插件,已内置实现了微博热搜,今日头条,今日早报和 AI 绘画函数插件。
 | 
					* 集成插件 API 功能,可结合大语言模型的 function 功能开发各种强大的插件,已内置实现了微博热搜,今日头条,今日早报和 AI 绘画函数插件。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 功能截图
 | 
					## 功能截图
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -16,34 +17,30 @@ ChatGLM,讯飞星火,文心一言等多个平台的大语言模型。集成了
 | 
				
			|||||||
 | 
					
 | 
				
			||||||

 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 新版聊天界面
 | 
					### AI 对话界面
 | 
				
			||||||
 | 
					
 | 
				
			||||||

 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### MidJourney 专业绘画界面(v3.1.3)
 | 
					### MidJourney 专业绘画界面
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||

 | 
					### Stable-Diffusion 专业绘画页面
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 绘图作品展
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### AI应用列表
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 自动调用函数插件
 | 
					### 自动调用函数插件
 | 
				
			||||||
 | 
					 | 
				
			||||||

 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||

 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 绘图作品展
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||

 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 登录页面
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||

 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 管理后台
 | 
					### 管理后台
 | 
				
			||||||
 | 
					
 | 
				
			||||||

 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||

 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||

 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 移动端 Web 页面
 | 
					### 移动端 Web 页面
 | 
				
			||||||
@@ -306,8 +303,8 @@ docker-compose up -d
 | 
				
			|||||||
 | 
					
 | 
				
			||||||

 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
最后登录前端聊天页面 [http://localhost:8080/chat](http://localhost:8080/chat)
 | 
					最后进入前端聊天页面 [http://localhost:8080/chat](http://localhost:8080/chat)
 | 
				
			||||||
你可以注册新用户,也可以使用系统默认有个账号:`geekmaster/12345678` 登录聊天。
 | 
					你可以注册新用户,也可以使用系统默认有个账号:`18575670125/12345678` 登录聊天。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
祝你使用愉快!!!
 | 
					祝你使用愉快!!!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -411,5 +408,7 @@ make clean linux
 | 
				
			|||||||

 | 
					
 | 
				
			||||||

 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -120,4 +120,5 @@ type SystemConfig struct {
 | 
				
			|||||||
	RewardImg       string   `json:"reward_img"`       // 众筹收款二维码地址
 | 
						RewardImg       string   `json:"reward_img"`       // 众筹收款二维码地址
 | 
				
			||||||
	EnabledFunction bool     `json:"enabled_function"` // 启用 API 函数功能
 | 
						EnabledFunction bool     `json:"enabled_function"` // 启用 API 函数功能
 | 
				
			||||||
	EnabledReward   bool     `json:"enabled_reward"`   // 启用众筹功能
 | 
						EnabledReward   bool     `json:"enabled_reward"`   // 启用众筹功能
 | 
				
			||||||
 | 
						DefaultModels   []string `json:"default_models"`   // 默认开通的 AI 模型
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -32,6 +32,7 @@ func (h *ChatModelHandler) Save(c *gin.Context) {
 | 
				
			|||||||
		Enabled   bool   `json:"enabled"`
 | 
							Enabled   bool   `json:"enabled"`
 | 
				
			||||||
		SortNum   int    `json:"sort_num"`
 | 
							SortNum   int    `json:"sort_num"`
 | 
				
			||||||
		Platform  string `json:"platform"`
 | 
							Platform  string `json:"platform"`
 | 
				
			||||||
 | 
							Weight    int    `json:"weight"`
 | 
				
			||||||
		CreatedAt int64  `json:"created_at"`
 | 
							CreatedAt int64  `json:"created_at"`
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if err := c.ShouldBindJSON(&data); err != nil {
 | 
						if err := c.ShouldBindJSON(&data); err != nil {
 | 
				
			||||||
@@ -39,7 +40,7 @@ func (h *ChatModelHandler) Save(c *gin.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	item := model.ChatModel{Platform: data.Platform, Name: data.Name, Value: data.Value, Enabled: data.Enabled}
 | 
						item := model.ChatModel{Platform: data.Platform, Name: data.Name, Value: data.Value, Enabled: data.Enabled, SortNum: data.SortNum, Weight: data.Weight}
 | 
				
			||||||
	item.Id = data.Id
 | 
						item.Id = data.Id
 | 
				
			||||||
	if item.Id > 0 {
 | 
						if item.Id > 0 {
 | 
				
			||||||
		item.CreatedAt = time.Unix(data.CreatedAt, 0)
 | 
							item.CreatedAt = time.Unix(data.CreatedAt, 0)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -67,6 +67,7 @@ func (h *UserHandler) Save(c *gin.Context) {
 | 
				
			|||||||
		Calls       int      `json:"calls"`
 | 
							Calls       int      `json:"calls"`
 | 
				
			||||||
		ImgCalls    int      `json:"img_calls"`
 | 
							ImgCalls    int      `json:"img_calls"`
 | 
				
			||||||
		ChatRoles   []string `json:"chat_roles"`
 | 
							ChatRoles   []string `json:"chat_roles"`
 | 
				
			||||||
 | 
							ChatModels  []string `json:"chat_models"`
 | 
				
			||||||
		ExpiredTime string   `json:"expired_time"`
 | 
							ExpiredTime string   `json:"expired_time"`
 | 
				
			||||||
		Status      bool     `json:"status"`
 | 
							Status      bool     `json:"status"`
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -81,12 +82,13 @@ func (h *UserHandler) Save(c *gin.Context) {
 | 
				
			|||||||
		user.Id = data.Id
 | 
							user.Id = data.Id
 | 
				
			||||||
		// 此处需要用 map 更新,用结构体无法更新 0 值
 | 
							// 此处需要用 map 更新,用结构体无法更新 0 值
 | 
				
			||||||
		res = h.db.Model(&user).Updates(map[string]interface{}{
 | 
							res = h.db.Model(&user).Updates(map[string]interface{}{
 | 
				
			||||||
			"mobile":          data.Mobile,
 | 
								"mobile":           data.Mobile,
 | 
				
			||||||
			"calls":           data.Calls,
 | 
								"calls":            data.Calls,
 | 
				
			||||||
			"img_calls":       data.ImgCalls,
 | 
								"img_calls":        data.ImgCalls,
 | 
				
			||||||
			"status":          data.Status,
 | 
								"status":           data.Status,
 | 
				
			||||||
			"chat_roles_json": utils.JsonEncode(data.ChatRoles),
 | 
								"chat_roles_json":  utils.JsonEncode(data.ChatRoles),
 | 
				
			||||||
			"expired_time":    utils.Str2stamp(data.ExpiredTime),
 | 
								"chat_models_json": utils.JsonEncode(data.ChatModels),
 | 
				
			||||||
 | 
								"expired_time":     utils.Str2stamp(data.ExpiredTime),
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		salt := utils.RandString(8)
 | 
							salt := utils.RandString(8)
 | 
				
			||||||
@@ -97,6 +99,7 @@ func (h *UserHandler) Save(c *gin.Context) {
 | 
				
			|||||||
			Salt:        salt,
 | 
								Salt:        salt,
 | 
				
			||||||
			Status:      true,
 | 
								Status:      true,
 | 
				
			||||||
			ChatRoles:   utils.JsonEncode(data.ChatRoles),
 | 
								ChatRoles:   utils.JsonEncode(data.ChatRoles),
 | 
				
			||||||
 | 
								ChatModels:  utils.JsonEncode(data.ChatModels),
 | 
				
			||||||
			ExpiredTime: utils.Str2stamp(data.ExpiredTime),
 | 
								ExpiredTime: utils.Str2stamp(data.ExpiredTime),
 | 
				
			||||||
			ChatConfig: utils.JsonEncode(types.UserChatConfig{
 | 
								ChatConfig: utils.JsonEncode(types.UserChatConfig{
 | 
				
			||||||
				ApiKeys: map[types.Platform]string{
 | 
									ApiKeys: map[types.Platform]string{
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,8 +24,22 @@ func NewChatModelHandler(app *core.AppServer, db *gorm.DB) *ChatModelHandler {
 | 
				
			|||||||
// List 模型列表
 | 
					// List 模型列表
 | 
				
			||||||
func (h *ChatModelHandler) List(c *gin.Context) {
 | 
					func (h *ChatModelHandler) List(c *gin.Context) {
 | 
				
			||||||
	var items []model.ChatModel
 | 
						var items []model.ChatModel
 | 
				
			||||||
	var cms = make([]vo.ChatModel, 0)
 | 
						var chatModels = make([]vo.ChatModel, 0)
 | 
				
			||||||
	res := h.db.Where("enabled = ?", true).Order("sort_num ASC").Find(&items)
 | 
						// 只加载用户订阅的 AI 模型
 | 
				
			||||||
 | 
						user, err := utils.GetLoginUser(c, h.db)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							resp.NotAuth(c)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var models []string
 | 
				
			||||||
 | 
						err = utils.JsonDecode(user.ChatModels, &models)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							resp.ERROR(c, "当前用户没有订阅任何模型")
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res := h.db.Where("enabled = ?", true).Where("value IN ?", models).Order("sort_num ASC").Find(&items)
 | 
				
			||||||
	if res.Error == nil {
 | 
						if res.Error == nil {
 | 
				
			||||||
		for _, item := range items {
 | 
							for _, item := range items {
 | 
				
			||||||
			var cm vo.ChatModel
 | 
								var cm vo.ChatModel
 | 
				
			||||||
@@ -34,11 +48,11 @@ func (h *ChatModelHandler) List(c *gin.Context) {
 | 
				
			|||||||
				cm.Id = item.Id
 | 
									cm.Id = item.Id
 | 
				
			||||||
				cm.CreatedAt = item.CreatedAt.Unix()
 | 
									cm.CreatedAt = item.CreatedAt.Unix()
 | 
				
			||||||
				cm.UpdatedAt = item.UpdatedAt.Unix()
 | 
									cm.UpdatedAt = item.UpdatedAt.Unix()
 | 
				
			||||||
				cms = append(cms, cm)
 | 
									chatModels = append(chatModels, cm)
 | 
				
			||||||
			} else {
 | 
								} else {
 | 
				
			||||||
				logger.Error(err)
 | 
									logger.Error(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	resp.SUCCESS(c, cms)
 | 
						resp.SUCCESS(c, chatModels)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -393,7 +393,7 @@ func (h *ChatHandler) doRequest(ctx context.Context, req types.ApiRequest, platf
 | 
				
			|||||||
		req.Messages = nil
 | 
							req.Messages = nil
 | 
				
			||||||
		break
 | 
							break
 | 
				
			||||||
	case types.Baidu:
 | 
						case types.Baidu:
 | 
				
			||||||
		apiURL = h.App.ChatConfig.Baidu.ApiURL
 | 
							apiURL = strings.Replace(h.App.ChatConfig.Baidu.ApiURL, "{model}", req.Model, 1)
 | 
				
			||||||
		break
 | 
							break
 | 
				
			||||||
	default:
 | 
						default:
 | 
				
			||||||
		apiURL = h.App.ChatConfig.OpenAI.ApiURL
 | 
							apiURL = h.App.ChatConfig.OpenAI.ApiURL
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -82,12 +82,13 @@ func (h *UserHandler) Register(c *gin.Context) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	salt := utils.RandString(8)
 | 
						salt := utils.RandString(8)
 | 
				
			||||||
	user := model.User{
 | 
						user := model.User{
 | 
				
			||||||
		Password:  utils.GenPassword(data.Password, salt),
 | 
							Password:   utils.GenPassword(data.Password, salt),
 | 
				
			||||||
		Avatar:    "/images/avatar/user.png",
 | 
							Avatar:     "/images/avatar/user.png",
 | 
				
			||||||
		Salt:      salt,
 | 
							Salt:       salt,
 | 
				
			||||||
		Status:    true,
 | 
							Status:     true,
 | 
				
			||||||
		Mobile:    data.Mobile,
 | 
							Mobile:     data.Mobile,
 | 
				
			||||||
		ChatRoles: utils.JsonEncode([]string{"gpt"}), // 默认只订阅通用助手角色
 | 
							ChatRoles:  utils.JsonEncode([]string{"gpt"}),               // 默认只订阅通用助手角色
 | 
				
			||||||
 | 
							ChatModels: utils.JsonEncode(h.App.SysConfig.DefaultModels), // 默认开通的模型
 | 
				
			||||||
		ChatConfig: utils.JsonEncode(types.UserChatConfig{
 | 
							ChatConfig: utils.JsonEncode(types.UserChatConfig{
 | 
				
			||||||
			ApiKeys: map[types.Platform]string{
 | 
								ApiKeys: map[types.Platform]string{
 | 
				
			||||||
				types.OpenAI:  "",
 | 
									types.OpenAI:  "",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,4 +7,5 @@ type ChatModel struct {
 | 
				
			|||||||
	Value    string // API Key 的值
 | 
						Value    string // API Key 的值
 | 
				
			||||||
	SortNum  int
 | 
						SortNum  int
 | 
				
			||||||
	Enabled  bool
 | 
						Enabled  bool
 | 
				
			||||||
 | 
						Weight   int // 对话权重,每次对话扣减多少次对话额度
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,6 +11,7 @@ type User struct {
 | 
				
			|||||||
	ImgCalls    int    // 剩余绘图次数
 | 
						ImgCalls    int    // 剩余绘图次数
 | 
				
			||||||
	ChatConfig  string `gorm:"column:chat_config_json"` // 聊天配置 json
 | 
						ChatConfig  string `gorm:"column:chat_config_json"` // 聊天配置 json
 | 
				
			||||||
	ChatRoles   string `gorm:"column:chat_roles_json"`  // 聊天角色
 | 
						ChatRoles   string `gorm:"column:chat_roles_json"`  // 聊天角色
 | 
				
			||||||
 | 
						ChatModels  string `gorm:"column:chat_models_json"` // AI 模型,不同的用户拥有不同的聊天模型
 | 
				
			||||||
	ExpiredTime int64  // 账户到期时间
 | 
						ExpiredTime int64  // 账户到期时间
 | 
				
			||||||
	Status      bool   `gorm:"default:true"` // 当前状态
 | 
						Status      bool   `gorm:"default:true"` // 当前状态
 | 
				
			||||||
	LastLoginAt int64  // 最后登录时间
 | 
						LastLoginAt int64  // 最后登录时间
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,4 +6,6 @@ type ChatModel struct {
 | 
				
			|||||||
	Name     string `json:"name"`
 | 
						Name     string `json:"name"`
 | 
				
			||||||
	Value    string `json:"value"`
 | 
						Value    string `json:"value"`
 | 
				
			||||||
	Enabled  bool   `json:"enabled"`
 | 
						Enabled  bool   `json:"enabled"`
 | 
				
			||||||
 | 
						SortNum  int    `json:"sort_num"`
 | 
				
			||||||
 | 
						Weight   int    `json:"weight"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,6 +12,7 @@ type User struct {
 | 
				
			|||||||
	ImgCalls    int                  `json:"img_calls"`
 | 
						ImgCalls    int                  `json:"img_calls"`
 | 
				
			||||||
	ChatConfig  types.UserChatConfig `json:"chat_config"`   // 聊天配置
 | 
						ChatConfig  types.UserChatConfig `json:"chat_config"`   // 聊天配置
 | 
				
			||||||
	ChatRoles   []string             `json:"chat_roles"`    // 聊天角色集合
 | 
						ChatRoles   []string             `json:"chat_roles"`    // 聊天角色集合
 | 
				
			||||||
 | 
						ChatModels  []string             `json:"chat_models"`   // AI模型集合
 | 
				
			||||||
	ExpiredTime int64                `json:"expired_time"`  // 账户到期时间
 | 
						ExpiredTime int64                `json:"expired_time"`  // 账户到期时间
 | 
				
			||||||
	Status      bool                 `json:"status"`        // 当前状态
 | 
						Status      bool                 `json:"status"`        // 当前状态
 | 
				
			||||||
	LastLoginAt int64                `json:"last_login_at"` // 最后登录时间
 | 
						LastLoginAt int64                `json:"last_login_at"` // 最后登录时间
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										6
									
								
								database/update-v3.1.7.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					-- 增加字段保存用户开通的 AI 模型
 | 
				
			||||||
 | 
					ALTER TABLE `chatgpt_users` ADD `chat_models_json` TEXT NOT NULL COMMENT 'AI模型 json' AFTER `chat_roles_json`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- 为每个模型设置对话权重
 | 
				
			||||||
 | 
					ALTER TABLE `chatgpt_chat_models` ADD `weight` TINYINT(3) NOT NULL COMMENT '对话权重,每次对话扣减多少次对话额度' AFTER `enabled`;
 | 
				
			||||||
 | 
					UPDATE `chatgpt_chat_models` SET weight = 1;
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								docs/imgs/admin_config.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 236 KiB  | 
| 
		 Before Width: | Height: | Size: 201 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								docs/imgs/admin_models.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 197 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								docs/imgs/app-list.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 579 KiB  | 
| 
		 Before Width: | Height: | Size: 1.9 MiB  | 
							
								
								
									
										
											BIN
										
									
								
								docs/imgs/mj_image.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 623 KiB  | 
| 
		 Before Width: | Height: | Size: 2.2 MiB  | 
							
								
								
									
										
											BIN
										
									
								
								docs/imgs/sd_image.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 622 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								docs/imgs/sd_image_detail.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 510 KiB  | 
							
								
								
									
										10
									
								
								web/src/assets/css/admin-form.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					.el-form-item__content .tip-input {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  width: 100%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.el-form-item__content .tip-input .input {
 | 
				
			||||||
 | 
					  width: 100%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.el-form-item__content .tip-input .info {
 | 
				
			||||||
 | 
					  margin-left: 6px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										15
									
								
								web/src/assets/css/admin-form.styl
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					.el-form-item__content {
 | 
				
			||||||
 | 
					  .tip-input {
 | 
				
			||||||
 | 
					    display flex
 | 
				
			||||||
 | 
					    width 100%
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .input {
 | 
				
			||||||
 | 
					      width 100%
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .info {
 | 
				
			||||||
 | 
					      margin-left 6px
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -209,7 +209,7 @@
 | 
				
			|||||||
  font-size: 20px;
 | 
					  font-size: 20px;
 | 
				
			||||||
  cursor: pointer;
 | 
					  cursor: pointer;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
.page-mj .inner .task-list-box .finish-job-list .job-item:hover {
 | 
					.page-mj .inner .task-list-box .finish-job-list .animate:hover {
 | 
				
			||||||
  box-shadow: 0 0 10px rgba(71,255,241,0.6); /* 添加阴影效果 */
 | 
					  box-shadow: 0 0 10px rgba(71,255,241,0.6); /* 添加阴影效果 */
 | 
				
			||||||
  transform: translateY(-10px); /* 向上移动10像素 */
 | 
					  transform: translateY(-10px); /* 向上移动10像素 */
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -132,7 +132,7 @@
 | 
				
			|||||||
  font-size: 20px;
 | 
					  font-size: 20px;
 | 
				
			||||||
  cursor: pointer;
 | 
					  cursor: pointer;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
.page-sd .inner .task-list-box .finish-job-list .job-item:hover {
 | 
					.page-sd .inner .task-list-box .finish-job-list .animate:hover {
 | 
				
			||||||
  box-shadow: 0 0 10px rgba(71,255,241,0.6); /* 添加阴影效果 */
 | 
					  box-shadow: 0 0 10px rgba(71,255,241,0.6); /* 添加阴影效果 */
 | 
				
			||||||
  transform: translateY(-10px); /* 向上移动10像素 */
 | 
					  transform: translateY(-10px); /* 向上移动10像素 */
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -80,8 +80,9 @@
 | 
				
			|||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .animate {
 | 
				
			||||||
      &:hover {
 | 
					      &:hover {
 | 
				
			||||||
        box-shadow: 0 0 10px rgba(71, 255, 241, 0.6); /* 添加阴影效果 */
 | 
					        box-shadow: 0 0 10px rgba(71, 255, 241, 0.6); /* 添加阴影效果 */
 | 
				
			||||||
        transform: translateY(-10px); /* 向上移动10像素 */
 | 
					        transform: translateY(-10px); /* 向上移动10像素 */
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -332,7 +332,7 @@
 | 
				
			|||||||
          <div class="finish-job-list">
 | 
					          <div class="finish-job-list">
 | 
				
			||||||
            <ItemList :items="finishedJobs" v-if="finishedJobs.length > 0" width="240" :gap="16">
 | 
					            <ItemList :items="finishedJobs" v-if="finishedJobs.length > 0" width="240" :gap="16">
 | 
				
			||||||
              <template #default="scope">
 | 
					              <template #default="scope">
 | 
				
			||||||
                <div class="job-item" @click="showTask(scope.item)">
 | 
					                <div class="job-item animate" @click="showTask(scope.item)">
 | 
				
			||||||
                  <el-image
 | 
					                  <el-image
 | 
				
			||||||
                      :src="scope.item['img_url']+'?imageView2/1/w/240/h/240/q/75'"
 | 
					                      :src="scope.item['img_url']+'?imageView2/1/w/240/h/240/q/75'"
 | 
				
			||||||
                      fit="cover"
 | 
					                      fit="cover"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,6 +14,7 @@
 | 
				
			|||||||
        </el-table-column>
 | 
					        </el-table-column>
 | 
				
			||||||
        <el-table-column prop="name" label="模型名称"/>
 | 
					        <el-table-column prop="name" label="模型名称"/>
 | 
				
			||||||
        <el-table-column prop="value" label="模型值"/>
 | 
					        <el-table-column prop="value" label="模型值"/>
 | 
				
			||||||
 | 
					        <el-table-column prop="weight" label="对话权重"/>
 | 
				
			||||||
        <el-table-column prop="enabled" label="启用状态">
 | 
					        <el-table-column prop="enabled" label="启用状态">
 | 
				
			||||||
          <template #default="scope">
 | 
					          <template #default="scope">
 | 
				
			||||||
            <el-switch v-model="scope.row['enabled']" @change="enable(scope.row)"/>
 | 
					            <el-switch v-model="scope.row['enabled']" @change="enable(scope.row)"/>
 | 
				
			||||||
@@ -59,6 +60,27 @@
 | 
				
			|||||||
          <el-input v-model="item.value" autocomplete="off"/>
 | 
					          <el-input v-model="item.value" autocomplete="off"/>
 | 
				
			||||||
        </el-form-item>
 | 
					        </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <el-form-item label="对话权重:" prop="weight">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          <template #default>
 | 
				
			||||||
 | 
					            <div class="tip-input">
 | 
				
			||||||
 | 
					              <el-input-number :min="1" v-model="item.weight" autocomplete="off"/>
 | 
				
			||||||
 | 
					              <div class="info">
 | 
				
			||||||
 | 
					                <el-tooltip
 | 
				
			||||||
 | 
					                    class="box-item"
 | 
				
			||||||
 | 
					                    effect="dark"
 | 
				
			||||||
 | 
					                    content="对话权重,每次对话扣减多少次对话额度"
 | 
				
			||||||
 | 
					                    placement="right"
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                  <el-icon>
 | 
				
			||||||
 | 
					                    <InfoFilled/>
 | 
				
			||||||
 | 
					                  </el-icon>
 | 
				
			||||||
 | 
					                </el-tooltip>
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					          </template>
 | 
				
			||||||
 | 
					        </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <el-form-item label="启用状态:" prop="enable">
 | 
					        <el-form-item label="启用状态:" prop="enable">
 | 
				
			||||||
          <el-switch v-model="item.enabled"/>
 | 
					          <el-switch v-model="item.enabled"/>
 | 
				
			||||||
        </el-form-item>
 | 
					        </el-form-item>
 | 
				
			||||||
@@ -79,7 +101,7 @@ import {onMounted, reactive, ref} from "vue";
 | 
				
			|||||||
import {httpGet, httpPost} from "@/utils/http";
 | 
					import {httpGet, httpPost} from "@/utils/http";
 | 
				
			||||||
import {ElMessage} from "element-plus";
 | 
					import {ElMessage} from "element-plus";
 | 
				
			||||||
import {dateFormat, removeArrayItem} from "@/utils/libs";
 | 
					import {dateFormat, removeArrayItem} from "@/utils/libs";
 | 
				
			||||||
import {Plus} from "@element-plus/icons-vue";
 | 
					import {InfoFilled, Plus} from "@element-plus/icons-vue";
 | 
				
			||||||
import {Sortable} from "sortablejs";
 | 
					import {Sortable} from "sortablejs";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 变量定义
 | 
					// 变量定义
 | 
				
			||||||
@@ -136,9 +158,11 @@ onMounted(() => {
 | 
				
			|||||||
      sortedData.forEach((id, index) => {
 | 
					      sortedData.forEach((id, index) => {
 | 
				
			||||||
        ids.push(parseInt(id))
 | 
					        ids.push(parseInt(id))
 | 
				
			||||||
        sorts.push(index)
 | 
					        sorts.push(index)
 | 
				
			||||||
 | 
					        items.value[index].sort_num = index
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      httpPost("/api/admin/model/sort", {ids: ids, sorts: sorts}).catch(e => {
 | 
					      httpPost("/api/admin/model/sort", {ids: ids, sorts: sorts}).then(() => {
 | 
				
			||||||
 | 
					      }).catch(e => {
 | 
				
			||||||
        ElMessage.error("排序失败:" + e.message)
 | 
					        ElMessage.error("排序失败:" + e.message)
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -148,7 +172,7 @@ onMounted(() => {
 | 
				
			|||||||
const add = function () {
 | 
					const add = function () {
 | 
				
			||||||
  title.value = "新增模型"
 | 
					  title.value = "新增模型"
 | 
				
			||||||
  showDialog.value = true
 | 
					  showDialog.value = true
 | 
				
			||||||
  item.value = {}
 | 
					  item.value = {enabled: true, weight: 1}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const edit = function (row) {
 | 
					const edit = function (row) {
 | 
				
			||||||
@@ -197,6 +221,7 @@ const remove = function (row) {
 | 
				
			|||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="stylus" scoped>
 | 
					<style lang="stylus" scoped>
 | 
				
			||||||
 | 
					@import "@/assets/css/admin-form.styl"
 | 
				
			||||||
.list {
 | 
					.list {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  .opt-box {
 | 
					  .opt-box {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -87,6 +87,38 @@
 | 
				
			|||||||
            </template>
 | 
					            </template>
 | 
				
			||||||
          </el-input>
 | 
					          </el-input>
 | 
				
			||||||
        </el-form-item>
 | 
					        </el-form-item>
 | 
				
			||||||
 | 
					        <el-form-item label="默认AI模型" prop="default_models">
 | 
				
			||||||
 | 
					          <template #default>
 | 
				
			||||||
 | 
					            <div class="tip-input">
 | 
				
			||||||
 | 
					              <el-select
 | 
				
			||||||
 | 
					                  v-model="system['default_models']"
 | 
				
			||||||
 | 
					                  multiple
 | 
				
			||||||
 | 
					                  :filterable="true"
 | 
				
			||||||
 | 
					                  placeholder="选择AI模型,多选"
 | 
				
			||||||
 | 
					                  style="width: 100%"
 | 
				
			||||||
 | 
					              >
 | 
				
			||||||
 | 
					                <el-option
 | 
				
			||||||
 | 
					                    v-for="item in models"
 | 
				
			||||||
 | 
					                    :key="item.id"
 | 
				
			||||||
 | 
					                    :label="item.name"
 | 
				
			||||||
 | 
					                    :value="item.value"
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					              </el-select>
 | 
				
			||||||
 | 
					              <div class="info">
 | 
				
			||||||
 | 
					                <el-tooltip
 | 
				
			||||||
 | 
					                    class="box-item"
 | 
				
			||||||
 | 
					                    effect="dark"
 | 
				
			||||||
 | 
					                    content="新用户注册默认开通的 AI 模型"
 | 
				
			||||||
 | 
					                    placement="right"
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                  <el-icon>
 | 
				
			||||||
 | 
					                    <InfoFilled/>
 | 
				
			||||||
 | 
					                  </el-icon>
 | 
				
			||||||
 | 
					                </el-tooltip>
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					          </template>
 | 
				
			||||||
 | 
					        </el-form-item>
 | 
				
			||||||
        <el-form-item>
 | 
					        <el-form-item>
 | 
				
			||||||
          <el-button type="primary" @click="save('system')">保存</el-button>
 | 
					          <el-button type="primary" @click="save('system')">保存</el-button>
 | 
				
			||||||
        </el-form-item>
 | 
					        </el-form-item>
 | 
				
			||||||
@@ -198,6 +230,7 @@ const chat = ref({
 | 
				
			|||||||
const loading = ref(true)
 | 
					const loading = ref(true)
 | 
				
			||||||
const systemFormRef = ref(null)
 | 
					const systemFormRef = ref(null)
 | 
				
			||||||
const chatFormRef = ref(null)
 | 
					const chatFormRef = ref(null)
 | 
				
			||||||
 | 
					const models = ref([])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
onMounted(() => {
 | 
					onMounted(() => {
 | 
				
			||||||
  // 加载系统配置
 | 
					  // 加载系统配置
 | 
				
			||||||
@@ -233,6 +266,12 @@ onMounted(() => {
 | 
				
			|||||||
    ElMessage.error("加载聊天配置失败: " + e.message)
 | 
					    ElMessage.error("加载聊天配置失败: " + e.message)
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  httpGet('/api/admin/model/list').then(res => {
 | 
				
			||||||
 | 
					    models.value = res.data
 | 
				
			||||||
 | 
					  }).catch(e => {
 | 
				
			||||||
 | 
					    ElMessage.error("获取模型失败:" + e.message)
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const rules = reactive({
 | 
					const rules = reactive({
 | 
				
			||||||
@@ -293,6 +332,7 @@ const uploadRewardImg = (file) => {
 | 
				
			|||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="stylus" scoped>
 | 
					<style lang="stylus" scoped>
 | 
				
			||||||
 | 
					@import "@/assets/css/admin-form.styl"
 | 
				
			||||||
.system-config {
 | 
					.system-config {
 | 
				
			||||||
  display flex
 | 
					  display flex
 | 
				
			||||||
  justify-content center
 | 
					  justify-content center
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -104,6 +104,23 @@
 | 
				
			|||||||
          </el-select>
 | 
					          </el-select>
 | 
				
			||||||
        </el-form-item>
 | 
					        </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <el-form-item label="模型权限" prop="chat_models">
 | 
				
			||||||
 | 
					          <el-select
 | 
				
			||||||
 | 
					              v-model="user.chat_models"
 | 
				
			||||||
 | 
					              multiple
 | 
				
			||||||
 | 
					              :filterable="true"
 | 
				
			||||||
 | 
					              placeholder="选择AI模型,多选"
 | 
				
			||||||
 | 
					          >
 | 
				
			||||||
 | 
					            <el-option
 | 
				
			||||||
 | 
					                v-for="item in models"
 | 
				
			||||||
 | 
					                :key="item.id"
 | 
				
			||||||
 | 
					                :label="item.name"
 | 
				
			||||||
 | 
					                :value="item.value"
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					          </el-select>
 | 
				
			||||||
 | 
					        </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <el-form-item label="启用状态">
 | 
					        <el-form-item label="启用状态">
 | 
				
			||||||
          <el-switch v-model="user.status"/>
 | 
					          <el-switch v-model="user.status"/>
 | 
				
			||||||
        </el-form-item>
 | 
					        </el-form-item>
 | 
				
			||||||
@@ -155,9 +172,10 @@ const query = ref({username: '', mobile: '', page: 1, page_size: 15})
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const title = ref('添加用户')
 | 
					const title = ref('添加用户')
 | 
				
			||||||
const add = ref(true)
 | 
					const add = ref(true)
 | 
				
			||||||
const user = ref({chat_roles: []})
 | 
					const user = ref({chat_roles: [], chat_models: []})
 | 
				
			||||||
const pass = ref({username: '', password: '', id: 0})
 | 
					const pass = ref({username: '', password: '', id: 0})
 | 
				
			||||||
const roles = ref([])
 | 
					const roles = ref([])
 | 
				
			||||||
 | 
					const models = ref([])
 | 
				
			||||||
const showUserEditDialog = ref(false)
 | 
					const showUserEditDialog = ref(false)
 | 
				
			||||||
const showResetPassDialog = ref(false)
 | 
					const showResetPassDialog = ref(false)
 | 
				
			||||||
const rules = reactive({
 | 
					const rules = reactive({
 | 
				
			||||||
@@ -170,6 +188,7 @@ const rules = reactive({
 | 
				
			|||||||
    {type: 'number', message: '请输入有效数字'},
 | 
					    {type: 'number', message: '请输入有效数字'},
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
  chat_roles: [{required: true, message: '请选择聊天角色', trigger: 'change'}],
 | 
					  chat_roles: [{required: true, message: '请选择聊天角色', trigger: 'change'}],
 | 
				
			||||||
 | 
					  chat_models: [{required: true, message: '请选择AI模型', trigger: 'change'}],
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
const loading = ref(true)
 | 
					const loading = ref(true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -183,6 +202,12 @@ onMounted(() => {
 | 
				
			|||||||
  }).catch(() => {
 | 
					  }).catch(() => {
 | 
				
			||||||
    ElMessage.error("获取聊天角色失败");
 | 
					    ElMessage.error("获取聊天角色失败");
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  httpGet('/api/admin/model/list').then(res => {
 | 
				
			||||||
 | 
					    models.value = res.data
 | 
				
			||||||
 | 
					  }).catch(e => {
 | 
				
			||||||
 | 
					    ElMessage.error("获取模型失败:" + e.message)
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const fetchUserList = function (page, pageSize) {
 | 
					const fetchUserList = function (page, pageSize) {
 | 
				
			||||||
 
 | 
				
			|||||||